I have a Python Flask application on RHEL 7 that I'm using to convert SVGs to PNGs through Wand. I was running into problems with font handling, but with Inkscape installed that fixed the issue. The SVG renderer switched automatically as expected.
I can run this code locally on my Mac, but when I do the text has been bolded and italicized, and all elements have a black border. I've installed Inkscape, and I've checked that MagickWand is correctly using Inkscape by converting with the verbose option.
$ convert -verbose test.svg test2.png
'inkscape' '/var/folders/8l/_jk6d2tn5cj6jp_rmq2t1tv92d_68s/T/magick-rLyLMnsG3Cxr89_XRGiGKQxpGFjJHxei' --export-filename='/var/folders/8l/_jk6d2tn5cj6jp_rmq2t1tv92d_68s/T/magick-HBEGlfBWo_vEuH6xZQh4BWPMWrTnNiKo.png' --export-dpi='96' --export-background='rgb(100%,100%,100%)' --export-background-opacity='1' > '/var/folders/8l/_jk6d2tn5cj6jp_rmq2t1tv92d_68s/T/magick-sepZd7k1yyDocNZD2p2RiJGdUnw6pKts' 2>&1
/var/folders/8l/_jk6d2tn5cj6jp_rmq2t1tv92d_68s/T/magick-HBEGlfBWo_vEuH6xZQh4BWPMWrTnNiKo.png PNG 300x150 300x150+0+0 8-bit sRGB 633B 0.010u 0:00.002
The problem seems to be with Wand, but I have no idea why it would render differently.
The (abridged) Flask code looks like this:
from base64 import b64encode
from flask import Flask, jsonify, request
from flask_cors import CORS
from wand.image import Image
def convert_svg_png(svg):
img = Image(blob=svg.encode(), format='svg', resolution=300)
png_data = img.make_blob(format='png')
data = b64encode(png_data)
return data.decode('ascii')
app = Flask(__name__)
CORS(app)
@app.route('/svgpng', methods=['POST'])
def svgpng():
content = request.json
imgData = convert_svg_png(content["svg"])
response = jsonify({'image': imgData})
response.headers.add('Access-Control-Allow-Origin', '*')
return response
For example here's an SVG image I'm trying to convert:
It looks exactly the same converted to PNG when I'm running the above code on RHEL7. But on Mac it looks like this:
The above image was generated from the following request:
curl --location --request POST 'http://localhost:5000/svgpng' \
--header 'Content-Type: application/json' \
--data-raw '{
"svg": "<svg width=\"1280\" height=\"550\" xmlns=\"http://www.w3.org/2000/svg\"><g transform=\"translate(15, 100)\"><line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"100\" stroke=\"#a0a6a4\"></line><line x1=\"233.5\" y1=\"0\" x2=\"233.5\" y2=\"100\" stroke=\"#e8e8e8\"></line><line x1=\"467\" y1=\"0\" x2=\"467\" y2=\"100\" stroke=\"#e8e8e8\"></line><g><g><rect x=\"0\" y=\"15.925925925925924\" width=\"46.7\" height=\"68.14814814814815\" fill=\"#A90A24\"></rect><text x=\"23.35\" y=\"55\" fill=\"#FFFFFF\" font-family=\"'\''Bliss Pro'\''\" text-anchor=\"middle\">2</text></g><g><rect x=\"46.7\" y=\"15.925925925925924\" width=\"23.349999999999994\" height=\"68.14814814814815\" fill=\"#B2B5B6\"></rect><text x=\"58.375\" y=\"55\" fill=\"#FFFFFF\" font-family=\"'\''Bliss Pro'\''\" text-anchor=\"middle\">1</text></g><g><rect x=\"70.05\" y=\"15.925925925925924\" width=\"70.05\" height=\"68.14814814814815\" fill=\"#44A88B\"></rect><text x=\"105.07499999999999\" y=\"55\" fill=\"#FFFFFF\" font-family=\"'\''Bliss Pro'\''\" text-anchor=\"middle\">3</text></g><g><rect x=\"140.1\" y=\"15.925925925925924\" width=\"23.349999999999994\" height=\"68.14814814814815\" fill=\"#F7B718\"></rect><text x=\"151.77499999999998\" y=\"55\" fill=\"#FFFFFF\" font-family=\"'\''Bliss Pro'\''\" text-anchor=\"middle\">1</text></g><g><rect x=\"163.45\" y=\"15.925925925925924\" width=\"303.55\" height=\"68.14814814814815\" fill=\"#D3E3DB\"></rect><text x=\"315.225\" y=\"55\" fill=\"#FFFFFF\" font-family=\"'\''Bliss Pro'\''\" text-anchor=\"middle\">13</text></g></g></g></svg>"
}'
This returns a base64 encoded image.
The issue seems to stem from how Wand is loading a blob. If I try a trivial example loading from a file on the Mac, it works fine.
with Image(filename='test.svg') as img:
with img.convert('png') as output_img:
output_img.save(filename='test-file.png')
Switch to loading a blob from a string and I see the black lines:
testsvg2 = '<svg width=\"10\" height=\"10\"><rect x=\"2\" y=\"2\" width=\"5\" height=\"5\" fill=\"#FFFFFF\"></rect></svg>'
with Image(blob=testsvg2.encode()) as img:
with img.convert('png') as output_img:
output_img.save(filename='test-str.png')
Now let's say I try looking at the image that Wand has loaded:
testsvg2 = '<svg width=\"10\" height=\"10\"><rect x=\"2\" y=\"2\" width=\"5\" height=\"5\" fill=\"#FFFFFF\"></rect></svg>'
with Image(blob=testsvg2.encode()) as img:
img.save(filename='svg-blob.svg')
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="10" height="10">
<g style="fill:black;fill-opacity:1;stroke:none;stroke-width:1;stroke-opacity:1;fill-rule:nonzero;">
<g style="fill:#FFFFFF;">
<rect x="2" y="2" width="5" height="5"/>
</g>
</g>
</svg>
...that's not the SVG I was trying to convert! Where did that <g>
element with the style attributes come from? That must be where it's coming from when converted to PNG, but how did that get there in the first place?
Here's my Mac setup:
- macOS Monterey 12.6
- Python 3.10.7
- Flask 2.2.2
- Wand 0.6.10
- ImageMagick 7.1.0-49 Q16-HDRI x86_64 2047
- Inkscape 1.2.1
Versus the RHEL7 setup:
- Red Hat Enterprise Linux Server release 7.9 (Maipo)
- Python 3.6.12
- Flask 2.0.1
- Wand 0.6.10
- ImageMagick 6.9.10-68 Q16 x86_64 2021-09-22
- Inkscape 0.92.2 (5c3e80d, 2017-08-06)
The only difference that doesn't look trivial to me is the Inkscape version, but again if it works with ImageMagick why not Wand too?