Created
December 11, 2017 14:58
-
-
Save nixeneko/5d4813d55f69cd2452130065dd1cb003 to your computer and use it in GitHub Desktop.
Draw font outline and shifted outlines
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
# coding: utf-8 | |
from fontTools.ttLib import TTFont | |
from PIL import Image, ImageDraw | |
import math | |
INFILE = "mplus-1p-regular.ttf" | |
ttf = TTFont(INFILE) | |
tags = ttf.reader.tables | |
unitsPerEm = ttf['head'].unitsPerEm | |
sTypoAscender = ttf['OS/2'].sTypoAscender | |
strdraw = "ねこ" | |
#ttf['cmap'].getBestCmap() #<- impremented in newest FontTools | |
#platFormID: 3 - Windows | |
#encodingID: 10 - UCS-4 | |
cmap = ttf['cmap'].getcmap(3,10).cmap | |
names = [cmap[ord(c)] for c in strdraw] | |
#ttf['hmtx'][<glyphName>]: (advanceWidth, lsb) | |
advanceWidths = [ttf['hmtx'][n][0] for n in names] | |
glyphs = [ttf['glyf'][n] for n in names] | |
#ttf['glyf']['a'].numberOfContours | |
#ttf['glyf']['a'].coordinates | |
#ttf['glyf']['a'].endPtsOfContours | |
#ttf['glyf']['a'].flags[idx] -> <flagOnCurve> | |
outlineData = [] | |
for g in glyphs: | |
#numberOfContours = g.numberOfContours | |
coordinates = g.coordinates | |
endPtsOfContours = g.endPtsOfContours | |
flags = g.flags | |
contours = [] | |
coords = list(zip(coordinates, flags)) | |
lastIdx = -1 | |
for idx in endPtsOfContours: | |
contours.append(coords[lastIdx+1:idx+1]) | |
lastIdx = idx | |
outlineData.append(contours) | |
#outlineData: [[((x, y), on-curve?), ...],...] | |
contours = [] | |
offsetX = 0 | |
for n in range(len(glyphs)): | |
for contour in outlineData[n]: | |
contours.append([((pos[0]+offsetX, sTypoAscender-pos[1]), flg) for pos, flg in contour]) | |
offsetX += advanceWidths[n] | |
# offsetX should be the width | |
height = unitsPerEm | |
width = offsetX | |
mag = 1 #image scaling factor =height/unitsPerEm | |
# 単位法線ベクトル | |
def shiftalongnormal(shiftval, contours): | |
contours_ret = [] | |
for contour in contours: | |
pts = [] | |
for i in range(len(contour)): | |
lastP, lastF = contour[i-1] | |
currP, currF = contour[i] | |
nextP, nextF = contour[(i+1)%len(contour)] | |
#unit normals | |
n0 = (currP[1]-lastP[1], lastP[0]-currP[0]) | |
a = math.sqrt( n0[0]**2 + n0[1]**2 ) | |
n0 = ( n0[0]/a, n0[1]/a ) | |
n1 = (nextP[1]-currP[1], currP[0]-nextP[0]) | |
a = math.sqrt( n1[0]**2 + n1[1]**2 ) | |
n1 = ( n1[0]/a, n1[1]/a ) | |
n = (n0[0]+n1[0], n0[1]+n1[1]) | |
a = math.sqrt( n[0]**2 + n[1]**2 ) | |
n = ( n[0]/a, n[1]/a ) | |
pt = (currP[0]+shiftval*n[0], currP[1]+shiftval*n[1]) | |
pts.append((pt, currF)) | |
contours_ret.append(pts) | |
return contours_ret | |
# 法線ベクトルを1/(1+cosθ)で調整 | |
def shiftalongnormal2(shiftval, contours): | |
contours_ret = [] | |
for contour in contours: | |
pts = [] | |
for i in range(len(contour)): | |
lastP, lastF = contour[i-1] | |
currP, currF = contour[i] | |
nextP, nextF = contour[(i+1)%len(contour)] | |
#unit normals | |
n0 = (currP[1]-lastP[1], lastP[0]-currP[0]) | |
a = math.sqrt( n0[0]**2 + n0[1]**2 ) | |
n0 = ( n0[0]/a, n0[1]/a ) | |
n1 = (nextP[1]-currP[1], currP[0]-nextP[0]) | |
a = math.sqrt( n1[0]**2 + n1[1]**2 ) | |
n1 = ( n1[0]/a, n1[1]/a ) | |
dp = n0[0]*n1[0] + n0[1]*n1[1] #dot product (n0, n1) | |
dist0 = math.sqrt(n0[0]**2+n0[1]**2) # ||n0|| | |
dist1 = math.sqrt(n1[0]**2+n1[1]**2) # ||n1|| | |
cosval = dp / (dist0*dist1) | |
f = max(0.1, 1+cosval) | |
n = ( (n0[0]+n1[0])/f, (n0[1]+n1[1])/f) | |
pt = (currP[0]+shiftval*n[0], currP[1]+shiftval*n[1]) | |
pts.append((pt, currF)) | |
contours_ret.append(pts) | |
return contours_ret | |
def drawquadbesier(draw, p0, p1, p2, col=(0,0,0)): | |
# divide a curve into two recursively using De Casteljau's algorithm | |
draw.point((int(p0[0]),int(p0[1])), col) | |
draw.point((int(p2[0]),int(p2[1])), col) | |
p01 = ( ( p0[0]+ p1[0])/2, ( p0[1]+ p1[1])/2 ) | |
p12 = ( ( p1[0]+ p2[0])/2, ( p1[1]+ p2[1])/2 ) | |
p012= ( (p01[0]+p12[0])/2, (p01[1]+p12[1])/2 ) | |
draw.point((int(p012[0]),int(p012[1])), col) | |
# if the distance of the vertices is far, draw recursively. | |
if (int(p0[0])-int(p012[0]))**2 + (int(p0[1])-int(p012[1]))**2 > 2: | |
drawquadbesier(draw, p0, p01, p012, col) | |
if (int(p2[0])-int(p012[0]))**2 + (int(p2[1])-int(p012[1]))**2 > 2: | |
drawquadbesier(draw, p012, p12, p2, col) | |
def draw_contours(draw, contours, col=(0,0,0)): | |
for contour in contours: | |
pts = [] | |
for i in range(len(contour)): | |
lastP, lastF = contour[i-1] | |
currP, currF = contour[i] | |
nextP, nextF = contour[(i+1) % len(contour)] | |
lastP = (lastP[0]*mag, lastP[1]*mag) | |
currP = (currP[0]*mag, currP[1]*mag) | |
nextP = (nextP[0]*mag, nextP[1]*mag) | |
if currF == 0: # off-curve point | |
# insert an on-curve point between two adjacent off-curve point | |
if lastF == 0: # off-curve | |
lastP = ( (currP[0]+lastP[0])/2, (currP[1]+lastP[1])/2 ) | |
if nextF == 0: # off-curve | |
nextP = ( (currP[0]+nextP[0])/2, (currP[1]+nextP[1])/2 ) | |
drawquadbesier(draw, lastP, currP, nextP, col) | |
elif lastF == currF == 1: # straight line | |
draw.line([(int(lastP[0]),int(lastP[1])), | |
(int(currP[0]),int(currP[1]))], | |
col, width=1) | |
# 単位法線ベクトル | |
im = Image.new("RGB", (width, height), (255,255,255)) | |
draw = ImageDraw.Draw(im) | |
draw_contours(draw, contours) | |
for i in range(1,10): | |
draw_contours(draw, shiftalongnormal(8*i, contours), (25*i,255-25*i,128)) | |
for i in range(1,9): | |
draw_contours(draw, shiftalongnormal(-5*i, contours), (128,255-25*i,25*i)) | |
im.save("out1.png") | |
# 法線ベクトルを1/(1+cosθ)で調整 | |
im = Image.new("RGB", (width, height), (255,255,255)) | |
draw = ImageDraw.Draw(im) | |
draw_contours(draw, contours) | |
for i in range(1,10): | |
draw_contours(draw, shiftalongnormal2(8*i, contours), (25*i,255-25*i,128)) | |
for i in range(1,9): | |
draw_contours(draw, shiftalongnormal2(-4*i, contours), (128,255-25*i,25*i)) | |
im.save("out2.png") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment