20190730

fontファイルの文字データ(グリフ)を、matplotlib の path (Bezier 曲線の制御点) ではなくx, y 座標に直しplotする

このページの内容は

  1. フォントファイルの文字から、ベジェ曲線の制御ポイントを取りだして、matplotlibのpathに変換する。
  2. N次ベジェ曲線の制御点→x,y座標の配列に変換
→(matplotlib.pyployt.plotでフォントが描ける!)

やる前に、IPAフォントをインストールしてね。

IPAフォントのアドレス。これをインストールする。→'C:\WINDOWS\Fonts\IPAM.TTF'が保存される。これがなかったら、下のコードは動かない。

フォントファイルのグリフから、ベジェ曲線の制御ポイントを取り出し、matplotlibのpathに変換。

ベクターフォントのフォントファイルの各文字データ(グリフ)から、ベジェ曲線の制御ポイントを取り出す方法を知った。
これで、フォントの中の文字データ(グリフ)の、ベジェ曲線の制御ポイントを取り出して作図できる。
IPAフォントを作図した。


手順は、

  1. IPAフォントのインストール。
  2. フォントのベクターの制御点の座標を、fonttoolsのrecordingpenを使って取り出す。
  3. recordingpen をmatplotlibのpathに変換する関数を書く
  4. matplotlibのpath(ベジェ曲線を描く機能)を使ってプロットする。

手順2で作ったpathデータを、matplotlibにベジェ曲線を描く機能でそのまま使った。(matplotlibのpathを使うと、制御点を指定するだけで曲線が描ける。)
わざわざ、matplotlibで読める形に直す必要があったけど、ベジェ曲線の式を理解していなくてもベジェ曲線が描けてしまった。matplotlibすごい!

#!/usr/bin/env python
# -*- coding: cp932 -*-

import time
from datetime import datetime as dt

import numpy as np

import
matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'IPAGothic'

import matplotlib.path as mpath
import matplotlib.patches as mpatches
from fontTools.ttLib import TTFont

font = TTFont(r'C:\Windows\Fonts\ipamp.ttf')
glyph_set = font.getGlyphSet()
cmap = font.getBestCmap()

def get_glyph(glyph_set, cmap, char):
glyph_name = cmap[ord(char)]
return glyph_set[glyph_name]

iroha_1 = get_glyph(glyph_set, cmap, u'て')

from fontTools.pens.recordingPen import RecordingPen

recording_pen = RecordingPen()

iroha_1.draw(recording_pen)
print(recording_pen.value)

pathdt = recording_pen.value # path(bezier curve)

def pdTopd(pd,mtrx1=[[1,0],[0,1]]):
tmp1 = [[]]
tmp2 = [[]]
i2 = 0
for i in range(len(pd)):
if pd[i][0] == 'closePath':
tmp1.append([])
tmp2.append([])
i2+=1

if pd[i][0]=='moveTo':
tmp1[i2].append(mpath.Path.MOVETO)
tmp2[i2].append(np.dot(mtrx1, pd[i][1][0]))

elif pd[i][0]=='qCurveTo':
tmp1[i2].append(mpath.Path.CURVE3)
tmp2[i2].append(np.dot(mtrx1, pd[i][1][0]))
tmp1[i2].append(mpath.Path.CURVE3)
tmp2[i2].append(np.dot(mtrx1, pd[i][1][1]))
if pd[i+1][0]=='qCurveTo':
tmp1[i2].append(mpath.Path.LINETO)
tmp2[i2].append(np.dot(mtrx1, pd[i][1][1]))

elif pd[i][0]=='lineTo':
tmp1[i2].append(mpath.Path.LINETO)
tmp2[i2].append(np.dot(mtrx1, pd[i][1][0]))

elif pd[i][0] == 'curveTo':
tmp1[i2].append(mpath.Path.CURVE4)
tmp2[i2].append(np.dot(mtrx1, pd[i][1][0]))
tmp1[i2].append(mpath.Path.CURVE4)
tmp2[i2].append(np.dot(mtrx1, pd[i][1][1]))
tmp1[i2].append(mpath.Path.CURVE4)
tmp2[i2].append(np.dot(mtrx1, pd[i][1][2]))
if pd[i+1][0]=='qCurveTo':
tmp1[i2].append(mpath.Path.LINETO)
tmp2[i2].append(np.dot(mtrx1, pd[i][1][0]))
print("%s/%s"%(i,len(pd)))

return tmp1, tmp2

a2,b2 = pdTopd(pathdt)# x,y(bezier)
a,b = pdTopd(pathdt,mtrx1=[[1,2],[0,1]])#(x,y(translated bezier))

f,(ax1) = plt.subplots(1,1)

for i in range(len(a)-1):
c = mpath.Path(b[i],a[i])
d = mpatches.PathPatch(c, facecolor='white', edgecolor="gray", lw=2, clip_on=False)
ax1.add_patch(d)
c = mpath.Path(b2[i],a2[i])
d = mpatches.PathPatch(c, facecolor='white', lw=2, clip_on=False)
ax1.add_patch(d)

ax1.set_aspect('equal')
ax1.grid(which="major", color="k", linestyle="--")
ax1.hlines(0, -3000,3000)
ax1.vlines(0, -3000,3000)
ax1.set_xlim(-2000, 2000)
ax1.set_ylim(-2000, 2000)
ax1.set_title(ur"Font", fontsize=30)
ax1.set_xlabel(ur"横 [px]", fontsize=30)
ax1.set_ylabel(ur"縦 [px]", fontsize=30)

tdatetime = dt.now()
itext = tdatetime.strftime('%Y%m%d'+ time.ctime()[11:13] + time.ctime()[14:16] + time.ctime()[17:19])
f.set_size_inches(19.2,10.8)
f.savefig("%s.png"%(itext))

20190727190844.png

N次ベジェ曲線

まずはベジェ曲線のプログラムから。

ベジェ曲線の説明には、このサイトが詳しい

あとベジェ曲線のwikipedia

二項定理とか出てきたので難しかった。
3次と4次しか使わないが、N次ベジェ曲線のx,y座標を取得するプログラムができた。

このコードは、

  1. 適当なベジェ曲線の制御点のx座標配列、y座標配列を取得して
  2. 関数部分でN次ベジェ曲線の制御ポイントをx,y座標に変換して
  3. matplotlib.pyplot.plotのx,y座標に入れる。

という作業をやっています。

N次ベジェ曲線の関数の引数は、x(N個の制御点x座標), y(N個の制御点y座標), と制御点間の補間数。(tを何分割するか。tが何なのかは、ほかのサイトを参照してください。)
当然ですが、x,yは、同じ長さにしてください。x,yの長さで、何次のベジェ曲線になるかが決まります。

x,y求めてプロット

曲線の間の補間がいまいちだとカクカクになる。

このカクカク感がたまんない!ベジェ曲線だが、ここまで間引くと、もはや曲線っぽくない。

20190601124605.png

#!/usr/bin/env python
# -*- coding: cp932 -*-

import
math
import time
from datetime import datetime as dt
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'IPAGothic'
from fontTools.ttLib import TTFont
from fontTools.pens.recordingPen import RecordingPen

def Nbezier(x1, y1, N=100):
if len(x1)!=len(y1):
print("Two lists must be the same 1-d list.")
return
if len(x1)<=2:
print("Length of the list must be grater than 2.")
return
t=np.linspace(0,1,N)
x2, y2 = [], []
for i in range(N):
tmpx, tmpy = 0, 0
n = len(x1)
for j in range(n):
tmpx = tmpx +(1-t[i])**(n-j-1)*t[i]**(j)*x1[j]*math.factorial(n-1)/(math.factorial(j)*math.factorial(n-j-1))
tmpy = tmpy + (1-t[i])**(n-j-1)*t[i]**(j)*y1[j]*math.factorial(n-1)/(math.factorial(j)*math.factorial(n-j-1))
x2.append(tmpx)
y2.append(tmpy)
return x2, y2

font = TTFont(r'C:\WINDOWS\Fonts\IPAM.TTF')#windowsのフォントファイル
glyph_set = font.getGlyphSet()
cmap = font.getBestCmap()

def get_glyph(glyph_set, cmap, char):
glyph_name = cmap[ord(char)]
return glyph_set[glyph_name]

iroha_1 = get_glyph(glyph_set, cmap, u'い')

recording_pen = RecordingPen()

iroha_1.draw(recording_pen)
pathdt = recording_pen.value

# パスデータをx,y座標データに変換する関数。
# ベジェ曲線の関数はこの関数の中で使っている。

def
PdBzToPlt(pd, N=7):
x=[]
y=[]
j=-1
for i in range(len(pd)):
tmpx=[]
tmpy=[]
if pd[i][0] == 'moveTo':
x.append([pathdt[i][1][0][0]])
y.append([pathdt[i][1][0][1]])
j+=1
elif pd[i][0] == 'qCurveTo':
tmpx.append(x[j][-1])##最初の点は、xの最終点から借りてくる。
tmpx.append(pd[i][1][0][0])
tmpx.append(pd[i][1][1][0])
tmpy.append(y[j][-1])
tmpy.append(pd[i][1][0][1])
tmpy.append(pd[i][1][1][1])
tmpx2, tmpy2 = Nbezier(tmpx, tmpy ,N)
x[j].extend(tmpx2[1:])#最初の点は、xの最終点から借りてきた点なので[1:]。
y[j].extend(tmpy2[1:])
elif pd[i][0] == 'qCurveTo':
tmpx.append(x[j][-1])
tmpx.append(pd[i][1][0][0])
tmpx.append(pd[i][1][1][0])
tmpx.append(pd[i][1][2][0])
tmpy.append(y[j][-1])
tmpy.append(pd[i][1][0][1])
tmpy.append(pd[i][1][1][1])
tmpy.append(pd[i][1][2][1])
tmpx2, tmpy2 = Nbezier(tmpx, tmpy ,N)
x[j].extend(tmpx2[1:])
y[j].extend(tmpy2[1:])
elif pd[i][0] == 'lineTo':
tmpx2 = [x[j][-1], pd[i][1][0][0]]
tmpy2 = [y[j][-1], pd[i][1][0][1]]
x[j].extend(tmpx2[1:])
y[j].extend(tmpy2[1:])
return x,y

x2,y2=PdBzToPlt(pathdt, N=7)

f,(ax1) = plt.subplots(1,1)

for i in range(len(x2)):
ax1.plot(x2[i],y2[i])
ax1.scatter(x2[i],y2[i],facecolor=[0,0,0,0.3],edgecolor=[0,0,0])#plt.show()

tdatetime = dt.now()
timtext = tdatetime.strftime('%Y%m%d'+ time.ctime()[11:13] + time.ctime()[14:16] + time.ctime()[17:19])
fname = "%s.png"%(timtext)
plt.savefig(fname)
plt.close()


来月は、フォントファイルを、x,y座標に変換し、せん断変形した後で、
x,y座標から、面積を求めようと思います。
x,y座標から、面積だけでなく、断面二次モーメントも求めようと思います。

先月に書いたシンプソン則云々は、断面二次モーメントのところで使う。8月の記事までには形にしたい。
続きを読む
posted by yuchan at 00:00 | Comment(337) | python