Created
October 6, 2022 10:43
-
-
Save simoncozens/91bd6525f5c653b87c82b59822e12685 to your computer and use it in GitHub Desktop.
Three-dimensional colour variable fonts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
import matplotlib.pyplot as plt | |
import math | |
from babelfont import Font, Layer, Shape, Master, Node, Axis, Glyph | |
import uuid | |
from lottie2vf.paintcompiler import compile_paints | |
from fontTools.ttLib import TTFont | |
glyph_pts = { | |
"H": np.array( | |
[ | |
[-216, -350, 100, 1], | |
[-216, 350, 100, 1], | |
[-113, 350, 100, 1], | |
[-113, 39, 100, 1], | |
[184, 39, 100, 1], | |
[184, 350, 100, 1], | |
[287, 350, 100, 1], | |
[287, -350, 100, 1], | |
[184, -350, 100, 1], | |
[184, -61, 100, 1], | |
[-113, -61, 100, 1], | |
[-113, -350, 100, 1], | |
] | |
), | |
"H.side": np.array( | |
[ | |
[-216, -350, 100, 1], | |
[-216, 350, 100, 1], | |
[-216, 350, 150, 1], | |
[-216, -350, 150, 1], | |
] | |
), | |
"H.insideL": np.array( | |
[ | |
[-113, -350, 100, 1], | |
[-113, 350, 100, 1], | |
[-113, 350, 150, 1], | |
[-113, -350, 150, 1], | |
] | |
), | |
"H.insideR": np.array( | |
[ | |
[184, -350, 100, 1], | |
[184, 350, 100, 1], | |
[184, 350, 150, 1], | |
[184, -350, 150, 1], | |
] | |
), | |
"H.side2": np.array( | |
[ | |
[287, -350, 100, 1], | |
[287, 350, 100, 1], | |
[287, 350, 150, 1], | |
[287, -350, 150, 1], | |
] | |
), | |
"H.bar1": np.array( | |
[ | |
[-113, 39, 100, 1], | |
[184, 39, 100, 1], | |
[184, 39, 150, 1], | |
[-113, 39, 150, 1], | |
] | |
), | |
"H.bar2": np.array( | |
[ | |
[-113, -61, 100, 1], | |
[184, -61, 100, 1], | |
[184, -61, 150, 1], | |
[-113, -61, 150, 1], | |
] | |
), | |
} | |
glyph_pts["H.back"] = glyph_pts["H"] + np.array([0, 0, 50, 0]) | |
in_t = np.array( | |
[ | |
[1, 0, 0, 0], | |
[0, 1, 0, -150], | |
[0, 0, 1, -125], | |
[0, 0, 0, 1], | |
] | |
) | |
out_t = np.array( | |
[ | |
[1, 0, 0, 0], | |
[0, 1, 0, 150], | |
[0, 0, 1, 125], | |
[0, 0, 0, 1], | |
] | |
) | |
def xrot(theta): | |
theta = math.radians(theta) | |
return np.array( | |
[ | |
[1, 0, 0, 0], | |
[0, math.cos(theta), -math.sin(theta), 0], | |
[0, math.sin(theta), math.cos(theta), 0], | |
[0, 0, 0, 1], | |
] | |
) | |
def yrot(theta): | |
theta = math.radians(theta) | |
ct = math.cos(theta) | |
st = math.sin(theta) | |
return np.array([[ct, 0, st, 0], [0, 1, 0, 0], [-st, 0, ct, 0], [0, 0, 0, 1]]) | |
def zrot(theta): | |
theta = math.radians(theta) | |
ct = math.cos(theta) | |
st = math.sin(theta) | |
return np.array([[ct, -st, 0, 0], [st, ct, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) | |
font = Font() | |
steps = range(-10, 10, 2) | |
font.axes = [ | |
Axis(tag="XROT", min=min(steps), max=max(steps), default=0, name="X Rotation"), | |
Axis(tag="YROT", min=min(steps), max=max(steps), default=0, name="Y Rotation"), | |
Axis(tag="ZROT", min=min(steps), max=max(steps), default=0, name="Z Rotation"), | |
] | |
def np_to_layer(points, master): | |
layer = Layer(width=1000, id=str(uuid.uuid1())) | |
layer._master = master.id | |
shape = Shape(nodes=[]) | |
for (x, y, z, _) in points: | |
shape.nodes.append( | |
Node( | |
x=min(1000, max(-1000, int(100 * x / z))), | |
y=min(1000, max(-1000, int(100 * y / z))), | |
type="l", | |
) | |
) | |
layer.shapes.append(shape) | |
return layer | |
master_map = {} | |
for x_angle in steps: | |
for y_angle in steps: | |
for z_angle in steps: | |
location = {"XROT": x_angle, "YROT": y_angle, "ZROT": z_angle} | |
m = Master( | |
location=location, | |
name="_".join(["%s%i" % (k, v) for k, v in location.items()]), | |
id=str(uuid.uuid1()), | |
) | |
master_map[tuple(location.items())] = m | |
m.font = font | |
font.masters.append(m) | |
for glyph_name, pts in glyph_pts.items(): | |
cps = [] | |
if glyph_name == "H": | |
cps = [ord("H")] | |
glyph = Glyph(name=glyph_name, codepoints=cps) | |
font.glyphs.append(glyph) | |
for x_angle in steps: | |
for y_angle in steps: | |
for z_angle in steps: | |
location = {"XROT": x_angle, "YROT": y_angle, "ZROT": z_angle} | |
points = [ | |
out_t | |
@ ((xrot(x_angle) @ yrot(y_angle) @ zrot(z_angle)) @ (in_t @ row)) | |
for row in pts | |
] | |
m = master_map[tuple(location.items())] | |
glyph.layers.append(np_to_layer(points, m)) | |
font.save("threed.ttf") | |
font = TTFont("threed.ttf") | |
compile_paints( | |
font, | |
""" | |
toplight = PaintRadialGradient( | |
(0, 0), | |
500, | |
(550, 600), | |
250, | |
ColorLine( | |
"#333333FF", | |
"#FFFFFFFF", | |
extend="pad", | |
)) | |
light = PaintRadialGradient( | |
(0, 0), | |
500, | |
(550, 600), | |
250, | |
ColorLine( | |
"#333333CC", | |
"#FFFFFFCC", | |
extend="pad", | |
)) | |
darkerlight = PaintRadialGradient( | |
(0, 0), | |
500, | |
(550, 600), | |
250, | |
ColorLine( | |
"#111111EE", | |
"#777777EE", | |
extend="pad", | |
)) | |
brighterlight = PaintRadialGradient( | |
(0, 0), | |
500, | |
(550, 600), | |
250, | |
ColorLine( | |
"#999999CC", | |
"#FFFFFFCC", | |
extend="pad", | |
)) | |
testlight = PaintRadialGradient( | |
(0, 0), | |
500, | |
(550, 600), | |
250, | |
ColorLine( | |
"#999999AA", | |
"#333333AA", | |
extend="pad", | |
)) | |
glyphs["H"] = PaintTranslate(500,400, | |
PaintComposite( | |
"src_over", | |
PaintColrLayers([ | |
PaintGlyph("H.bar2",testlight), | |
PaintGlyph("H.bar1",testlight), | |
]), | |
PaintComposite( | |
"src_over", | |
PaintColrLayers([ | |
PaintGlyph("H.side",light), | |
PaintGlyph("H.side2",light), | |
PaintGlyph("H.insideL",brighterlight), | |
PaintGlyph("H.insideR",darkerlight), | |
PaintGlyph("H",toplight), | |
]), | |
PaintGlyph("H.back",light) | |
) | |
)) | |
""", | |
) | |
font.save("threed.ttf") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment