Конвертировать SVG в PNG с поддержкой преобразования CSS3 matrix3d
Мне нужно конвертировать SVG в PNG из скрипта Python. Для этого есть множество инструментов, которые я пробовал (я на Ubuntu):
inkscape
(Командная строка Inkscape)rsvg-convert
отlibrsvg2-bin
convert
(это ImageMagick)
Но ни один из них не поддерживает CSS3 transform: matrix3d(...)
, Единственное программное обеспечение, которое поддерживает это, я обнаружил до сих пор, это Firefox/Chrom[ium]/etc, но я, похоже, не разрешаю рендеринг командной строки в PNG.
Есть ли какие-либо специальные опции, которые я мог бы передать одному из вариантов выше, чтобы получить рендеринг с полной поддержкой CSS3? Или есть еще один вариант конвертации, о котором я сейчас не знаю?
Редактировать Я попробовал больше инструментов, в том числе:
wkhtmltoimage
часть http://wkhtmltopdf.org/- nodehot (с большим количеством слесарного дела, чтобы обслуживать SVG на локальном сервере и затем загрузить окончательное изображение)
И, возможно, хотя я не мог проверить, потому что это только OS X
webkit2png
(спасибо за предложение, @Mark Setchell)
Все вышеперечисленное не соответствует моему требованию, потому что они основаны на WebKit, а WebKit просто не поддерживает matrix3d в SVG, хотя он отлично подходит для обычных элементов...
1 ответ
Ура, я нашел решение - но оно имеет огромные накладные расходы и требует множества вещей:
Основная идея состоит в том, чтобы обслуживать ваш SVG в качестве веб-сайта, а затем отображать его с помощью SlimerJS, версии Firefox со скриптами. В отличие от других упомянутых подходов, для визуализации SVG используется Gecko, и, как упоминалось выше, Gecko (в отличие от WebKit) корректно отображает 3D-вращения CSS3 в SVG.
Возможно, вы тоже захотите использовать xvfb, поэтому вам не нужно видеть окно SlimerJS во время рендеринга (оно пока не поддерживает безголовый).
Служите SVG/HTML на локальном сервере
Во-первых, вам нужно представить свой SVG как изображение на странице HTML. Встроенный SVG или обычный SVG у меня не сработали. рекомендую http.server.BaseHTTPRequestHandler
, обслуживающий как HTML, так и обычный SVG (который будет запрошен во втором запросе).
html = """
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<img src="http://localhost:8000/svg/%s" />
</body>
</html>
""" % svg_name
margin: 0;
удаляет пространство по умолчанию вокруг любого веб-сайта.
Я запускаю сервер как Thread
с deamon=True
поэтому он будет закрыт, когда мой сценарий будет завершен.
class SvgServer:
def __init__(self):
self.server = http.server.HTTPServer(('', PORT), SvgRequestHandler)
self.server_thread = threading.Thread(target=self.server.serve_forever, daemon=True).start()
SvgRequestHandler
должен быть вашим экземпляром BaseHTTPRequestHandler
(Я предполагаю, что есть или будет способ прямого доступа к файлам из SlimerJS, потому что Firefox может сделать это с file://
но я не мог заставить его работать. Тогда этот шаг станет устаревшим.)
Рендеринг с помощью SlimerJS
Теперь, когда SVG доступен для браузера, мы можем назвать SlimerJS. SlimerJS принимает только файлы JavaScript в качестве входных данных, поэтому нам лучше сгенерировать немного JavaScript:
slimer_commands = """
var webpage = require('webpage').create();
webpage
.open('%s')
.then(function () {
webpage.viewportSize = { width: 1920, height: 1080 };
webpage.render('%s', { onlyViewport: true });
slimer.exit()
});
""" % (url_for_html_embedding_svg, output_file_name)
Бонус: пакетная обработка с Promises, это намного быстрее по сравнению с запуском отдельного SlimerJS для каждого SVG, который мы хотим визуализировать. Я лично работаю с индексированными SVG, меняю по мере необходимости.
slimer_command_head = "const { defer } = require('sdk/core/promise');" \
"var webpage = require('webpage').create();" \
"webpage.viewportSize = { width: 1920, height: 1080 };" \
"var deferred = defer();" \
"deferred.resolve();" \
"deferred.promise.then(function () {"
commands = [slimer_command_head]
for frame_index in range(frame_count):
command = "return webpage.open('%s'); }).then(function () { webpage.render('%s', { onlyViewport: true });" % (
'http://localhost:8000/html/%d' % frame_index,
FileManagement.png_file_path_for_frame(frame_index)
)
commands.append(command)
commands.append("slimer.exit(); });")
slimer_commands = ''.join(commands)
Теперь, когда у нас есть готовый скрипт, сохраните его в временном файле и выполните его:
with tempfile.NamedTemporaryFile(suffix='.js') as slimer_file:
slimer_file.write(bytes(slimer_commands, 'UTF-8'))
slimer_file.flush()
command = [
SLIMER_EXECUTABLE,
os.path.abspath(slimer_file.name)
]
if run_headless:
command.insert(0, 'xvfb-run')
os.system(' '.join(command))
run_headless
опция добавляет команду XVFB, чтобы перейти без головы.
Вы сделали
Это было легко, быстро и прямо, не так ли?
Если вы не могли по-настоящему следовать фрагментам кода, посмотрите исходный код проекта, для которого я его использовал.