Sending SVG Images by SMS and WhatsApp with Python and CairoSVG

July 27, 2020
Written by
Reviewed by
Diane Phan
Twilion

Sending SVG Images by SMS and WhatsApp with Python and CairoSVG

Twilio Programmable Messaging makes it easy to embed images in text messages, but a notable omission in the list of supported formats is Scalable Vector Graphics (SVG). While SVG is not one of the most popular image formats, it is the preferred choice for generating diagrams and charts, because this format renders without quality degradation at any resolution.

So what do you do if you have an SVG image and need to send it via SMS or WhatsApp? In this article I will show you how I use the CairoSVG package for Python to convert SVG images to the PNG format, which the messaging APIs support.

Tutorial requirements

The only requirement that you need for this tutorial is Python 3.6 or newer. If your operating system does not provide a Python interpreter, you can go to python.org to download an installer.

Installing CairoSVG

Let’s begin by creating a directory where we can implement this project:

$ mkdir svg2png
$ cd svg2png

Following best practices, we are now going to create a virtual environment where we will install our Python dependencies.

If you are using a Unix or MacOS system, open a terminal and enter the following commands to do the tasks described above:

$ python -m venv venv
$ source venv/bin/activate
(venv) $ pip install cairosvg flask

For those of you following the tutorial on Windows, enter the following commands in a command prompt window:

$ python -m venv venv
$ venv\Scripts\activate
(venv) $ pip install cairosvg flask

The Python package that we have installed are:

  • The cairosvg package, to render SVG files to PNG images that we can send through WhatsApp and SMS.
  • The Flask framework, to create the web application that can convert SVG images to PNG on the fly.

The CairoSVG package requires the cairo library installed in your system. The installation procedure for this library is different depending on the operating system, so follow the instructions that apply to your system below.

Installing Cairo on Linux

Under Linux your package manager will likely provide a packaged version of the Cairo graphics library, either under the name libcairo2, libcairo or maybe cairo.

If you are using Ubuntu Linux, you can install the libcairo2 package as follows:

$ sudo apt-get install libcairo2

Installing Cairo on Mac OS

The easiest way to install the Cairo library on a Mac computer is to use Homebrew. Assuming you have installed this package manager on your system, the command to install Cairo is:

$ brew install cairo

Installing Cairo on Windows

On Windows I had no luck finding an installer for the Cairo library, so I had to get creative. I knew that GIMP, a popular open-source photo editor, uses this library, so I installed this application and then searched for the Cairo DLL and all of its dependencies in the installation directory.

I’ve created two zip files with the ready to be used Cairo DLLs for you to download. Please choose the appropriate version of these libraries depending on the version of Python you are using:

Once you have the zip file downloaded, extract its contents on the project directory, or in any directory that is in the system path.

Converting SVG images with CairoSVG

If you don’t have an example SVG file that you can use in the examples in this section, feel free to download the one I’m using, which is a diagram of a chess board.

If you open this file in your web browser, you’ll notice that the file scales automatically as you resize the window.

Diagram of a chess board svg file on web browser

Let’s convert this file to the PNG format in a Python shell using the cairosvg.svg2png() function:

>>> import cairosvg
>>> with open('board.svg') as f:
...     cairosvg.svg2png(bytestring=f.read(), write_to='board.png')
...
>>>

Here we are passing the contents of the SVG file in the bytestring argument and the name of the output PNG file in the write_to argument. Open the board.png file in your browser and you will notice that unlike the SVG original this image has a fixed size.

The size of the generated PNG image will depend on the size hints included in the SVG file itself. The chess board SVG file that I’m using indicates a default size of 390 x 390 pixels, so that is the size of the resulting PNG. But as you saw before, SVG files can be rendered at any resolution without degradation, so we can tell CairoSVG to render it at any size by adding the parent_width and parent_height arguments:

>>> with open('board.svg') as f:
...     cairosvg.svg2png(bytestring=f.read(), write_to='board.png', parent_width=1000, parent_height=1000)
...
>>>

Implementing an image conversion endpoint

To send an image through the Twilio messaging APIs we don’t provide the image data as a file, instead we specify a URL from where Twilio downloads the image on its own.

We can implement an endpoint that converts and returns the PNG image on the fly by changing the write_to argument from a filename to a byte stream:

>>> from io import BytesIO
>>> import cairosvg
>>> png = BytesIO()
>>> with open('board.svg') as f:
...     cairosvg.svg2png(bytestring=f.read(), write_to=png)
…
>>>

When you run the conversion in this way you end up with the PNG data stored in memory. The actual PNG data stored in the png variable can be obtained with the expression png.getvalue(). This data can then be returned as the response of the endpoint.

To help you understand how such an endpoint can be implemented, below you can see a simple, yet complete, Flask application that uses the techniques shown in this article to perform on-the-fly conversion of an SVG image to PNG. If you want to test this application out, create a file in the root directory of your project named app.py and copy the code below to it:

from io import BytesIO
from flask import Flask, request
import cairosvg

app = Flask(__name__)


@app.route('/svg2png')
def svg2png():
    width = request.args.get('width', type=int, default=None)
    height = request.args.get('height', type=int, default=None)
    png = BytesIO()
    with open('board.svg') as f:
        cairosvg.svg2png(bytestring=f.read(), write_to=png,
                         parent_width=width, parent_height=height)
    return png.getvalue(), 200, {'Content-Type': 'image/png'}

The application takes optional width and height arguments, obtained from the query string of the request URL (you will see in a moment how to provide these arguments). It then converts the SVG image to PNG, writing the resulting output to a bytes stream. It finally returns the image data with a 200 status code and the Content-Type header set to image/png, which is the correct setting to tell the client (be it Twilio or a web browser) that this endpoint is returning data that should be interpreted as a PNG image.

Start the application as follows:

(venv) $ flask run

Then enter http://localhost:5000/svg2png in the address bar of your web browser to check that you are getting the converted image displayed in your browser.

localhost view of the svg2png webhook showing the chess board diagram

Since we haven’t specified a width or height the image in this case will render with the default size indicated in the SVG file.

Let’s now ask for the image to be generated in a specific size. In the address bar of your browser, enter http://localhost:5000/svg2png?width=200&height=200 to request that the image be resized to 200 x 200 pixels.

localhost view of the svg2png webhook showing a resized photo of the chess board diagram

So that’s it. Now we have a web endpoint that converts an SVG image on the fly to PNG and returns it in the proper way to be used by the Twilio messaging APIs!

Feel free to experiment with different SVG files, or if you prefer, use the file upload capabilities of the Flask framework to upload and convert your images in a single step. I have written a detailed tutorial on uploading images with Flask that will be useful if you want to implement this feature.

Conclusion

I hope that you learned some helpful techniques in this article. Would you like to see how these are applied to a real Twilio-based application? Check out my Play Chess with a Friend on WhatsApp tutorial on this blog!

Miguel Grinberg is a Python Developer for Technical Content at Twilio. Reach out to him at mgrinberg [at] twilio [dot] com if you have a cool Python project you’d like to share on this blog!