161 lines
4.0 KiB
Python
161 lines
4.0 KiB
Python
from PIL import Image, ImageFont, ImageDraw
|
|
import scipy.ndimage as nd
|
|
import numpy as np
|
|
|
|
from . import d2
|
|
|
|
# TODO: add support for newlines?
|
|
|
|
PIXELS = 2**22
|
|
|
|
|
|
def _load_image(thing):
|
|
if isinstance(thing, str):
|
|
return Image.open(thing)
|
|
elif isinstance(thing, (np.ndarray, np.generic)):
|
|
return Image.fromarray(thing)
|
|
return Image.fromarray(np.array(thing))
|
|
|
|
|
|
def measure_text(name, text, width=None, height=None):
|
|
font = ImageFont.truetype(name, 96)
|
|
x0, y0, x1, y1 = font.getbbox(text)
|
|
aspect = (x1 - x0) / (y1 - y0)
|
|
if width is None and height is None:
|
|
height = 1
|
|
if width is None:
|
|
width = height * aspect
|
|
if height is None:
|
|
height = width / aspect
|
|
return (width, height)
|
|
|
|
|
|
def measure_image(thing, width=None, height=None):
|
|
im = _load_image(thing)
|
|
w, h = im.size
|
|
aspect = w / h
|
|
if width is None and height is None:
|
|
height = 1
|
|
if width is None:
|
|
width = height * aspect
|
|
if height is None:
|
|
height = width / aspect
|
|
return (width, height)
|
|
|
|
|
|
@d2.sdf2
|
|
def text(font_name, text, width=None, height=None, pixels=PIXELS, points=512):
|
|
# load font file
|
|
font = ImageFont.truetype(font_name, points)
|
|
|
|
# compute texture bounds
|
|
p = 0.2
|
|
x0, y0, x1, y1 = font.getbbox(text)
|
|
px = int((x1 - x0) * p)
|
|
py = int((y1 - y0) * p)
|
|
tw = x1 - x0 + 1 + px * 2
|
|
th = y1 - y0 + 1 + py * 2
|
|
|
|
# render text to image
|
|
im = Image.new("L", (tw, th))
|
|
draw = ImageDraw.Draw(im)
|
|
draw.text((px - x0, py - y0), text, font=font, fill=255)
|
|
|
|
return _sdf(width, height, pixels, px, py, im)
|
|
|
|
|
|
@d2.sdf2
|
|
def image(thing, width=None, height=None, pixels=PIXELS):
|
|
im = _load_image(thing).convert("L")
|
|
return _sdf(width, height, pixels, 0, 0, im)
|
|
|
|
|
|
def _sdf(width, height, pixels, px, py, im):
|
|
tw, th = im.size
|
|
|
|
# downscale image if necessary
|
|
factor = (pixels / (tw * th)) ** 0.5
|
|
if factor < 1:
|
|
tw, th = int(round(tw * factor)), int(round(th * factor))
|
|
px, py = int(round(px * factor)), int(round(py * factor))
|
|
im = im.resize((tw, th))
|
|
|
|
# convert to numpy array and apply distance transform
|
|
im = im.convert("1")
|
|
a = np.array(im)
|
|
inside = -nd.distance_transform_edt(a)
|
|
outside = nd.distance_transform_edt(~a)
|
|
texture = np.zeros(a.shape)
|
|
texture[a] = inside[a]
|
|
texture[~a] = outside[~a]
|
|
|
|
# save debug image
|
|
# a = np.abs(texture)
|
|
# lo, hi = a.min(), a.max()
|
|
# a = (a - lo) / (hi - lo) * 255
|
|
# im = Image.fromarray(a.astype('uint8'))
|
|
# im.save('debug.png')
|
|
|
|
# compute world bounds
|
|
pw = tw - px * 2
|
|
ph = th - py * 2
|
|
aspect = pw / ph
|
|
if width is None and height is None:
|
|
height = 1
|
|
if width is None:
|
|
width = height * aspect
|
|
if height is None:
|
|
height = width / aspect
|
|
x0 = -width / 2
|
|
y0 = -height / 2
|
|
x1 = width / 2
|
|
y1 = height / 2
|
|
|
|
# scale texture distances
|
|
scale = width / tw
|
|
texture *= scale
|
|
|
|
# prepare fallback rectangle
|
|
# TODO: reduce size based on mesh resolution instead of dividing by 2
|
|
rectangle = d2.rectangle((width / 2, height / 2))
|
|
|
|
def f(p):
|
|
x = p[:, 0]
|
|
y = p[:, 1]
|
|
u = (x - x0) / (x1 - x0)
|
|
v = (y - y0) / (y1 - y0)
|
|
v = 1 - v
|
|
i = u * pw + px
|
|
j = v * ph + py
|
|
d = _bilinear_interpolate(texture, i, j)
|
|
q = rectangle(p).reshape(-1)
|
|
outside = (i < 0) | (i >= tw - 1) | (j < 0) | (j >= th - 1)
|
|
d[outside] = q[outside]
|
|
return d
|
|
|
|
return f
|
|
|
|
|
|
def _bilinear_interpolate(a, x, y):
|
|
x0 = np.floor(x).astype(int)
|
|
x1 = x0 + 1
|
|
y0 = np.floor(y).astype(int)
|
|
y1 = y0 + 1
|
|
|
|
x0 = np.clip(x0, 0, a.shape[1] - 1)
|
|
x1 = np.clip(x1, 0, a.shape[1] - 1)
|
|
y0 = np.clip(y0, 0, a.shape[0] - 1)
|
|
y1 = np.clip(y1, 0, a.shape[0] - 1)
|
|
|
|
pa = a[y0, x0]
|
|
pb = a[y1, x0]
|
|
pc = a[y0, x1]
|
|
pd = a[y1, x1]
|
|
|
|
wa = (x1 - x) * (y1 - y)
|
|
wb = (x1 - x) * (y - y0)
|
|
wc = (x - x0) * (y1 - y)
|
|
wd = (x - x0) * (y - y0)
|
|
|
|
return wa * pa + wb * pb + wc * pc + wd * pd
|