Drawing SVG Superellipses with Python

Superellipses are a little bit of a mathematical oddity, resembling rounded squares or square-ish circles. They are, however, becoming popular for icon outlines. iOS currently uses them, as do recent versions of Android. This article shows you how to use Python to create an arbitrary superellipse in SVG. This can then be used as an image mask to create a superelliptical icon.

Superellipses are sometimes mistakenly called “squircles”. True squircles are simply squares with rounded corners. A superellipse does not have any straight edges and has more of a continuous curve, eg.:

Superellipses come from a family of curves known as Lamé Curves. They are defined using the following equation:

axn + byn = 1

Where x,y are the coordinates, and a,b determine the superellipse’s size. n defines the superellipse’s order. Note that a superellipse of order 2, is in fact a regular ellipse. Most superellipses have n>2. A value of 1.0 will give a rhombus (or parallelogram), and values less that 1.0 give a 4-pointed star. See the diagram below for a sample selection of superellipse shapes. As n tends to infinity, the superellipse tends to the shape of a square (or rectangle). Typically values of about 3-5 are sufficient.

It is easier to computationally draw the curve if we parameterize it. In parameterized angular form, the above equation becomes:

x = A.cos2n θy = B.sin2n θ

A and B specify the radius in the x and y directions respectively.

Here is the implementation as a Python3 module:

#!/usr/bin/env python

""" SuperEllipse Drawing in SVG

(C) Copyright Richard Marsden, Winwaed Software Technology LLC 2018
Code is distributed under the Berkeley (BSD) '3 clause' license
"""


from math import pi,cos,sin,pow,fabs,copysign
import os
import sys

# You will need to import svgwrite using pip3 if you do not already have it
import svgwrite


# Returns x to the power y, but preserves the sign of x
# This allows us to plot all four quadrants with a single loop and
# no additional sign logic
def signed_power(x,y):
    return copysign( pow(fabs(x),y) ,x)


# Draws the super ellipse
#  svgdoc - the svgwrite SVG document
#  cx,cy - Center coordinate for the super ellipse
#  diax,diay - Diameter (x,y) for the super ellipse
#  npnts  - Number of points to use (greater => more accurate shape)
#  order - Order of Super Ellipse. 2=Ellipse. >2 for a super ellipse
#  linecol - line color as a string, eg "rgb(255,0,0)"
#  fillcol - fill color as a string, eg "rgb(255,0,0)"

def SuperEllipse(svgdoc, cx, cy, diax, diay, npnts, order, linecol, fillcol):

    pnts = []
    power = 2.0 / order
    theta = pi * 2.0 / npnts
    radiusx = diax/2.0
    radiusy = diay/2.0

    for i in range(0,npnts):    
        x = cx + radiusx * signed_power(cos( i*theta), power)
        y = cy + radiusy * signed_power(sin( i*theta), power)
        pnts.append( (x,y) )

    svgdoc.add(svgdoc.polygon(points= pnts,
                                   stroke_width = "1",
                                   stroke = linecol,
                                   fill = fillcol))
    return svgdoc



# Command Line Handling

if __name__ == '__main__' :

    if ( len(sys.argv) != 5):
        print("Incorrect parameters\nExpected usage:")
        print("python3 SuperEllipse.py <file> <order> <size> <num points>")
        print("eg.\npython3 SuperEllipse.py output.svg 3 300 50")
        exit(1)

    # creates a red super ellipse of the specified size and order    
    fname = sys.argv[1]
    print(fname)
    order = float(sys.argv[2])
    sz = int(sys.argv[3])
    npnts = int(sys.argv[4])
    svg_document = svgwrite.Drawing(filename = fname,
                                        size = (sz,sz))
    svg_document = SuperEllipse(svg_document, sz/2,sz/2, sz,sz, npnts, order, "rgb(0,0,0)", "rgb(255,0,0)")

    svg_document.save()

Note that this module can be imported, or called from the command line. The above red superellipse is produced with the following Python command:

python3 SuperEllipse.py output.svg 3 300 50

Here is a sample sheet of different order superellipses (the order is written underneath each shape):

A selection of Superellipses. Order is written beneath each shape.

It is produced with the following code:

#!/usr/bin/env python

"""
Demonstration of different super ellipse shapes

(C) Copyright Richard Marsden, Winwaed Software Technology LLC 2018
"""

import svgwrite
import SuperEllipse

dia = 100
npnts = 50


svg_document = svgwrite.Drawing(filename = "multiple.svg", size = (500,500))
svg_document.add(svg_document.rect(insert=(0,0), size=('100%','100%'), rx=None, ry=None, fill='rgb(255,255,255)'))

order = 0.3
svg_document = SuperEllipse.SuperEllipse(svg_document, 60,60, dia,dia, npnts, order, "rgb(0,0,0)", "rgb(255,0,0)")
svg_document.add(svg_document.text(str(order),insert=( 55,125)) )

order = 0.5
svg_document = SuperEllipse.SuperEllipse(svg_document, 180,60, dia,dia, npnts, order, "rgb(0,0,0)", "rgb(255,85,0)")
svg_document.add(svg_document.text(str(order),insert=( 175,125)) )

order = 1
svg_document = SuperEllipse.SuperEllipse(svg_document, 300,60, dia,dia, npnts, order, "rgb(0,0,0)", "rgb(255,170,0)")
svg_document.add(svg_document.text(str(order),insert=( 295,125)) )

order = 1.3
svg_document = SuperEllipse.SuperEllipse(svg_document, 60,180, dia,dia, npnts, order, "rgb(0,0,0)", "rgb(255,255,0)")
svg_document.add(svg_document.text(str(order),insert=( 55,245)) )

order = 1.7
svg_document = SuperEllipse.SuperEllipse(svg_document, 180,180, dia,dia, npnts, order, "rgb(0,0,0)", "rgb(220,221,0)")
svg_document.add(svg_document.text(str(order),insert=( 175,245)) )

order = 2.0
svg_document = SuperEllipse.SuperEllipse(svg_document, 300,180, dia,dia, npnts, order, "rgb(0,0,0)", "rgb(85,196,0)")
svg_document.add(svg_document.text(str(order),insert=( 295,245)) )

order = 3.0
svg_document = SuperEllipse.SuperEllipse(svg_document, 60,300, dia,dia, npnts, order, "rgb(0,0,0)", "rgb(0,255,0)")
svg_document.add(svg_document.text(str(order),insert=( 55,365)) )

order = 5.0
svg_document = SuperEllipse.SuperEllipse(svg_document, 180,300, dia,dia, npnts, order, "rgb(0,0,0)", "rgb(0,127,127)")
svg_document.add(svg_document.text(str(order),insert=( 175,365)) )

order = 10.0
svg_document = SuperEllipse.SuperEllipse(svg_document, 300,300, dia,dia, npnts, order, "rgb(0,0,0)", "rgb(0,0,255)")
svg_document.add(svg_document.text(str(order),insert=( 295,365)) )


svg_document.save()