Created
December 1, 2020 03:22
-
-
Save celestialphineas/06ebaaba4b689dd70704950ace3ee984 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
#!/usr/bin/env python3 | |
# -*- encoding: utf8 -*- | |
import cairocffi as cairo | |
import numpy as np | |
# Adding the implicit on-points back to the contour | |
# The input is one single contour, not all of the glyph's contours | |
def regularize_quadratic_contour(contour): | |
result = [] | |
if not contour[0]['on']: | |
if contour[-1]['on']: | |
result.append({ 'x': float(contour[-1]['x']), | |
'y': float(contour[-1]['y']), 'on': True }) | |
else: | |
first_x = contour[0]['x']; first_y = contour[0]['y'] | |
last_x = contour[-1]['x']; last_y = contour[-1]['y'] | |
result.append({ 'x': (first_x + last_x)/2, | |
'y': (first_y + last_y)/2, 'on': True }) | |
for i, pt in enumerate(contour[:-1]): | |
result.append({ 'x': float(pt['x']), 'y': float(pt['y']), 'on': pt['on'] }) | |
next_pt = contour[i+1] | |
if not pt['on'] and not next_pt['on']: | |
result.append({ 'x': (pt['x'] + next_pt['x'])/2, | |
'y': (pt['y'] + next_pt['y'])/2, 'on': True }) | |
# Do nothing when the last point is moved to the head | |
if contour[-1]['on'] and not contour[0]['on']: pass | |
else: result.append({ 'x': float(contour[-1]['x']), | |
'y': float(contour[-1]['y']), 'on': contour[-1]['on'] }) | |
# Making the top-rightest on-point as the starting point | |
max_on_i = 0; max_x = -float('inf'); max_y = -float('inf') | |
for i, pt in enumerate(result): | |
if pt['on']: | |
if pt['y'] > max_y: | |
max_on_i = i; max_x = pt['x']; max_y = pt['y'] | |
if pt['y'] == max_y and pt['x'] > max_x: | |
max_on_i = i; max_x = pt['x'] | |
return [] if not result else (result[max_on_i:] + result[:max_on_i]) | |
# Converting a quadratic Bezier segment to cubic | |
# Return a tuple of the control point coordinates | |
def quadratic2cubic(x0, y0, x1, y1, x2, y2): | |
cx1 = x0 + (x1-x0)*2/3; cy1 = y0 + (y1-y0)*2/3 | |
cx2 = x2 + (x1-x2)*2/3; cy2 = y2 + (y1-y2)*2/3 | |
return ( cx1, cy1, cx2, cy2 ) | |
# A routine for rasterizing quadratic contours | |
def rasterize_quadratic(contours, upm=1000, size=(1000, 1000)): | |
"""A routine for rasterizing quadratic contours | |
Parameters | |
---------- | |
contours: { "x": number, "y": number, "on": boolean }[][] | |
Representation of the glyph's contours | |
upm: number | |
Units per em-box, usually 1000, sometimes 256 or 1024 | |
The value varies by fonts | |
size: (number, number) | |
Shape of the output image | |
""" | |
h, w = size | |
# Regularized contours | |
cs = [ regularize_quadratic_contour(c) for c in contours ] | |
# Helper function for coordinate transformation and unpacking | |
coord = lambda pt: (pt['x']/upm*w, pt['y']/upm*h) | |
# Setting up cairo surface | |
surface = cairo.ImageSurface(cairo.FORMAT_A8, w, h) | |
ctx = cairo.Context(surface) | |
ctx.set_fill_rule(cairo.FILL_RULE_WINDING) | |
ctx.set_source_rgb(1, 1, 1) | |
# Draw the contours | |
for c in cs: | |
ctx.move_to(*coord(c[0])) | |
i = 0 | |
while i < len(c) - 1: | |
if not c[i]['on']: raise Exception('Invalid regularized quadratic.') | |
if not c[i+1]['on']: | |
end_pt = c[i+2] if i+2 < len(c) else c[0] | |
cps = quadratic2cubic(*coord(c[i]), *coord(c[i+1]), *coord(end_pt)) | |
ctx.curve_to(*cps, *coord(end_pt)) | |
i += 2 | |
else: | |
end_pt = c[i+1] if i+1 < len(c) else c[0] | |
ctx.line_to(*coord(end_pt)) | |
i += 1 | |
ctx.fill() | |
buf = surface.get_data() | |
arr = np.frombuffer(buf, dtype=np.uint8).reshape(h, w).copy() | |
return arr |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment