Mathematical Curves for Favicon Design
Table of Contents
Explorations of mathematical curves suitable for favicon and logo design. Each curve has distinct visual properties derived from its parametric equations.
1. Lissajous Curves
Parametric curves defined by: \(x = \sin(at)\), \(y = \sin(bt + \delta)\)
The ratio \(a:b\) determines the shape. When \(a\) and \(b\) are coprime integers, the curve closes after one period.
import numpy as np
from PIL import Image, ImageDraw
configs = [
(3, 2, "lissajous_3_2.png"),
(3, 4, "lissajous_3_4.png"),
(5, 4, "lissajous_5_4.png"),
(1, 2, "lissajous_1_2.png"),
]
for a, b, name in configs:
img = Image.new('RGBA', (64, 64), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
draw.rectangle([0, 0, 63, 63], fill=(30, 41, 59, 255))
t = np.linspace(0, 2 * np.pi, 500)
x = np.sin(a * t)
y = np.sin(b * t + np.pi/2)
x_scaled = 8 + ((x + 1) / 2) * 48
y_scaled = 8 + ((y + 1) / 2) * 48
points = list(zip(x_scaled, y_scaled))
for i in range(len(points) - 1):
progress = i / len(points)
r = int(56 + progress * 180)
g = int(189 - progress * 100)
b_col = int(248 - progress * 50)
draw.line([points[i], points[i+1]], fill=(r, g, b_col, 255), width=2)
img.save(f'research/curves/{name}')
print(f"Generated {name}")
| Ratio | Shape | Image |
|---|---|---|
| 3:2 | Bow | ![]() |
| 3:4 | Complex | ![]() |
| 5:4 | Intricate | ![]() |
| 1:2 | Parabolic | ![]() |
| 3:5 | 8-crossing | ![]() |
2. Lamé Curves (Superellipses)
Defined by: \(|x/a|^n + |y/b|^n = 1\)
The exponent \(n\) controls the shape:
- \(n < 1\): star/astroid
- \(n = 1\): diamond
- \(n = 2\): circle/ellipse
- \(n > 2\): squircle (rounded square)
import numpy as np
from PIL import Image, ImageDraw
def superellipse_points(n, num_points=500):
t = np.linspace(0, 2 * np.pi, num_points)
x = np.sign(np.cos(t)) * np.abs(np.cos(t)) ** (2/n)
y = np.sign(np.sin(t)) * np.abs(np.sin(t)) ** (2/n)
return x, y
configs = [
(0.5, "superellipse_astroid.png"),
(1.0, "superellipse_diamond.png"),
(2.0, "superellipse_circle.png"),
(2.5, "superellipse_squircle.png"),
(4.0, "superellipse_rounded.png"),
]
for n, name in configs:
img = Image.new('RGBA', (64, 64), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
draw.rectangle([0, 0, 63, 63], fill=(30, 41, 59, 255))
x, y = superellipse_points(n)
x_scaled = 10 + ((x + 1) / 2) * 44
y_scaled = 10 + ((y + 1) / 2) * 44
points = list(zip(x_scaled, y_scaled))
draw.polygon(points, fill=(56, 189, 248, 100))
for i in range(len(points) - 1):
draw.line([points[i], points[i+1]], fill=(255, 255, 255, 230), width=2)
draw.line([points[-1], points[0]], fill=(255, 255, 255, 230), width=2)
img.save(f'research/curves/{name}')
print(f"Generated {name}")
| n | Shape | Image |
|---|---|---|
| 0.5 | Astroid | ![]() |
| 1.0 | Diamond | ![]() |
| 2.0 | Circle | ![]() |
| 2.5 | Squircle | ![]() |
| 4.0 | Rounded | ![]() |
3. Lemniscate of Bernoulli
The figure-eight curve: \((x^2 + y^2)^2 = a^2(x^2 - y^2)\)
In polar form: \(r^2 = a^2 \cos(2\theta)\)
The horizontal orientation gives the infinity symbol \(\infty\).
import numpy as np
from PIL import Image, ImageDraw
def create_infinity(size=64):
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
draw.rectangle([0, 0, size-1, size-1], fill=(30, 41, 59, 255))
t = np.linspace(0, 2 * np.pi, 500)
denom = 1 + np.sin(t)**2
x = np.cos(t) / denom
y = np.sin(t) * np.cos(t) / denom
pad, w, h = 10, 44, 44
x_scaled = pad + ((x + 1) / 2) * w
y_scaled = pad + ((y + 0.5) / 1) * h
points = list(zip(x_scaled, y_scaled))
draw.polygon(points, fill=(56, 189, 248, 80))
for i in range(len(points) - 1):
draw.line([points[i], points[i+1]], fill=(255, 255, 255, 230), width=2)
return img
def create_lemniscate(size=64):
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
draw.rectangle([0, 0, size-1, size-1], fill=(30, 41, 59, 255))
theta = np.linspace(-np.pi/4, np.pi/4, 250)
r_sq = np.maximum(np.cos(2 * theta), 0)
r = np.sqrt(r_sq)
x1, y1 = r * np.cos(theta), r * np.sin(theta)
x2, y2 = -r * np.cos(theta), -r * np.sin(theta)
x = np.concatenate([x1, x2[::-1]])
y = np.concatenate([y1, y2[::-1]])
x_norm = (x - x.min()) / (x.max() - x.min())
y_norm = (y - y.min()) / (y.max() - y.min())
x_scaled = 8 + x_norm * 48
y_scaled = 8 + y_norm * 48
points = list(zip(x_scaled, y_scaled))
draw.polygon(points, fill=(251, 191, 36, 120))
n = len(points)
for i in range(n - 1):
progress = i / n
rc = int(56 + progress * 195)
gc = int(189 - progress * 10)
bc = int(248 - progress * 212)
draw.line([points[i], points[i+1]], fill=(rc, gc, bc, 255), width=2)
return img
create_infinity(64).save('research/curves/lemniscate_infinity.png')
create_lemniscate(64).save('research/curves/lemniscate_vertical.png')
print("Generated lemniscate variants")
| Variant | Image |
|---|---|
| Infinity (\(\infty\)) | ![]() |
| Vertical | ![]() |
4. Rose Curves
Polar curves: \(r = \cos(k\theta)\)
For odd \(k\), you get \(k\) petals. For even \(k\), you get \(2k\) petals. Inherently bounded and distinctive at small scale.
| k | Petals | Image |
|---|---|---|
| 3 | 3 | ![]() |
| 4 | 8 | ![]() |
| 5 | 5 | ![]() |
| 7 | 7 | ![]() |
5. Spirograph (Hypotrochoid)
Parametric curves from the classic Spirograph toy:
- \(x = (R-r)\cos(t) + d\cos\frac{(R-r)t}{r}\)
- \(y = (R-r)\sin(t) - d\sin\frac{(R-r)t}{r}\)
Hand-drawn aesthetic but mathematically precise – fits the literate-programming vibe.
| R:r:d | Lobes | Image |
|---|---|---|
| 5:3:2 | 5 | ![]() |
| 7:4:3 | 7 | ![]() |
| 8:5:3 | 8 | ![]() |
| 10:7:4 | 10 | ![]() |
6. Generation Script
Run all generators:
"""Generate all curve images for favicon exploration."""
import numpy as np
from PIL import Image, ImageDraw
import os
os.chdir(os.path.dirname(os.path.abspath(__file__)))
# Lissajous
for a, b, name in [(3,2,"lissajous_3_2.png"), (3,4,"lissajous_3_4.png"),
(5,4,"lissajous_5_4.png"), (1,2,"lissajous_1_2.png")]:
img = Image.new('RGBA', (64, 64), (30, 41, 59, 255))
draw = ImageDraw.Draw(img)
t = np.linspace(0, 2*np.pi, 500)
x, y = np.sin(a*t), np.sin(b*t + np.pi/2)
pts = list(zip(8 + ((x+1)/2)*48, 8 + ((y+1)/2)*48))
for i in range(len(pts)-1):
p = i/len(pts)
draw.line([pts[i], pts[i+1]], fill=(int(56+p*180), int(189-p*100), int(248-p*50), 255), width=2)
img.save(name)
print(f" {name}")
# Superellipses
for n, name in [(0.5,"superellipse_astroid.png"), (1.0,"superellipse_diamond.png"),
(2.0,"superellipse_circle.png"), (2.5,"superellipse_squircle.png"),
(4.0,"superellipse_rounded.png")]:
img = Image.new('RGBA', (64, 64), (30, 41, 59, 255))
draw = ImageDraw.Draw(img)
t = np.linspace(0, 2*np.pi, 500)
x = np.sign(np.cos(t)) * np.abs(np.cos(t))**(2/n)
y = np.sign(np.sin(t)) * np.abs(np.sin(t))**(2/n)
pts = list(zip(10 + ((x+1)/2)*44, 10 + ((y+1)/2)*44))
draw.polygon(pts, fill=(56, 189, 248, 100))
for i in range(len(pts)-1):
draw.line([pts[i], pts[i+1]], fill=(255, 255, 255, 230), width=2)
draw.line([pts[-1], pts[0]], fill=(255, 255, 255, 230), width=2)
img.save(name)
print(f" {name}")
# Lemniscates
img = Image.new('RGBA', (64, 64), (30, 41, 59, 255))
draw = ImageDraw.Draw(img)
t = np.linspace(0, 2*np.pi, 500)
denom = 1 + np.sin(t)**2
x, y = np.cos(t)/denom, np.sin(t)*np.cos(t)/denom
pts = list(zip(10 + ((x+1)/2)*44, 10 + ((y+0.5)/1)*44))
draw.polygon(pts, fill=(56, 189, 248, 80))
for i in range(len(pts)-1):
draw.line([pts[i], pts[i+1]], fill=(255, 255, 255, 230), width=2)
img.save("lemniscate_infinity.png")
print(" lemniscate_infinity.png")
img = Image.new('RGBA', (64, 64), (30, 41, 59, 255))
draw = ImageDraw.Draw(img)
theta = np.linspace(-np.pi/4, np.pi/4, 250)
r = np.sqrt(np.maximum(np.cos(2*theta), 0))
x = np.concatenate([r*np.cos(theta), -r[::-1]*np.cos(theta[::-1])])
y = np.concatenate([r*np.sin(theta), -r[::-1]*np.sin(theta[::-1])])
xn, yn = (x-x.min())/(x.max()-x.min()), (y-y.min())/(y.max()-y.min())
pts = list(zip(8 + xn*48, 8 + yn*48))
draw.polygon(pts, fill=(251, 191, 36, 120))
for i in range(len(pts)-1):
p = i/len(pts)
draw.line([pts[i], pts[i+1]], fill=(int(56+p*195), int(189-p*10), int(248-p*212), 255), width=2)
img.save("lemniscate_vertical.png")
print(" lemniscate_vertical.png")
print("Done.")
lissajous_3_2.png lissajous_3_4.png lissajous_5_4.png lissajous_1_2.png superellipse_astroid.png superellipse_diamond.png superellipse_circle.png superellipse_squircle.png superellipse_rounded.png lemniscate_infinity.png lemniscate_vertical.png Done.



















