にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

WindowsでChainerのセットアップ

ZOTAC GeForce GTX 1060を買った。ので、最近話題のchainerをセットアップしたメモ。

環境

OS: Windows 10 (64 bit)
Python: Python3 (Anaconda Windows 64-bit)

今までつけてたQuadro K600も刺してある。

GTX 1060のセットアップ

  • 取り付け
  • CDのドライバをインストー

Visual Studioのインストー

Visual Studio 2015は既に入っていたので省略。

CUDAのインストー

CUDA Toolkit 8.0のインストー

ウィザードに従ってインストールする。

もしかしてここで間違ってドライバを入れてしまったかもしれない。NVidia GeForce Experienceとかいうソフトが最新のドライバをサジェストしてくるので何度かインストールしたり再起動を繰り返したりなどした。

cuDNNのインストー

ダウンロードするためには登録が必要。

ダウンロードしてきたzipファイルの中身のcudaディレクトリ以下を

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0

の下に突っ込む。

Anaconda Pythonを最新に

Anacondaを最新版に更新。
Anaconda Promptを開いて

conda update conda
conda update --all

Chainerのインストー

環境変数

  • PATHに C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin
  • INCLUDEに C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt

を追加。

(追記: 下はv1の時の手順、あるいはv2でGPUを利用しない場合)
Anaconda Promptから

pip install chainer --no-cache-dir

と実行する。

2017-06-10 追記

Chainer 2.0から、CuPyとChainerが別パッケージに分離された。そのため、pipでchainerだけでなくcupyも一緒にインストールしないとGPUが使えない。

v1系から更新する場合には、Anaconda Promptから

pip uninstall chainer
pip install cupy chainer --no-cache-dir

とする。

(追記終)

テスト

chainer/example/mnist/ のMNISTのサンプルをダウンロードしてきて実行。

CPU (Core i7-3770)が20 iters/sec、GTX 1060が130~170 iters/sec、Quadro K600が50 iters/sec位だった。案外Quadro K600がGTX 1060の3分の1位の速度出てるっぽいし、CUDAコア数が10倍になったからといって速度が10倍にはならない。ボトルネックはどこだろう。

フォントサイズに合わせて回転するフォントを作る(2)

TrueType命令で遊ぶシリーズ。

はじめに

さて、前回作った回転するフォントでは回転の処理があまり賢くないので、それを改善するのがこの記事でやりたいことである。
そのために、三角関数のsin, cosの値を計算する関数を作成した。この関数を利用して回転行列の要素の値を計算し、それによって回転するという風にする。

成果物

f:id:nixeneko:20170205130752p:plain


きもい実にきもい。

ダウンロード

変更点

回転行列の値のStorage Areaへの格納

三角関数(sin, cos)を計算する関数を関数4としてもってきた。
他は、基本的に前回の回転するフォントそのままであるが、'prep'での回転行列の要素の値を格納する際に、PPEMの値を取得し、その角度に対する三角関数の計算を呼び出し、その結果を格納している。

'prep'
/* prep */
MPPEM         /* |ppem(int)| */
PUSHW_1 4096  /* |ppem(int)|64.0| */
MUL           /* |ppem(F26Dot6)| */
PUSHB_1 4     /* |ppem(F26Dot6)|4| */
CALL          /* |64*sin|64*cos| */
PUSHB_2 2 3   /* |64*sin|64*cos|2|3| */
ROLL          /* |64*sin|2|3|64*cos| */
WS            /* |64*sin|2| */ /* StorageArea[3] = 64*cos */
SWAP          /* |2|64*sin| */
WS            /* |          */ /* StorageArea[2] = 64*sin */

MPPEMで取得したppemの値に適当な係数を掛け算してやると、回転の度合いを調整できる。マイナスをかければ逆回転になる。

回転

関数2で回転した座標を計算する際に、前回はLOOPCALLを用いていたが、ここは今回は1回だけ回転させればいいので、CALLに変更している。
ついでに関数2で引数として余計なスタック要素が積まれてたのを削除している。

'fpgm'
PUSHB_1
 0
FDEF          /* ..| n-1 |             *//* ← initial stack */
PUSHB_1       /* ..| n-1 | 2 |         */ 
 2
ADD           /* ..| n+1 |             *//* n+1: phantom point n+1 に対応 */
SVTCA[x-axis]                            /* 座標の測定をx軸方向にする */
GC[orig]      /* ..| x_(n+1) |         *//* phantom point n+1の(X)座標取得 */
PUSHB_1       /* ..| x_(n+1) | 2.0 |   *//* Uint 128 == F24Dot6 2.0 */
 128
DIV           /* ..| x_(n+1) / 2 |     */
PUSHB_1       /* ..| x_(n+1) / 2 | 0 | */
 0
SWAP          /* ..| 0 | x_(n+1) / 2 | */
WS            /* ..|                   *//* StorageArea[0] = x_(n+1) / 2 */
PUSHB_1       /* ..| 1 |               */
 1
RCVT          /* ..| CVT[1] |          *//* read control value table */
PUSHB_1       /* ..| CVT[1] | 1 |      */     /* CVT[1]は回転中心y_cに対応 */
 1
SWAP          /* ..| 1 | CVT[1] |      */
WS            /* ..|                   *//* StorageArea[1] = CVT[1] */
ENDF

/* Function 1: Calculates rotated coordinates with pre-defined angle T */
/* initial stack ..| y | x |             x' = x*cosT - y*sinT          */
/* final stack   ..| y'| x'|      while  y' = x*sinT + y*cosT          */
PUSHB_1
 1
FDEF     /* ..|y|x|                                 */
DUP      /* ..|y|x|x|                               */
ROLL     /* ..|x|x|y|                               */
DUP      /* ..|x|x|y|y|                             */
ROLL     /* ..|x|y|y|x|                             */
PUSHB_1  /* ..|x|y|y|x|3|                           */
 3
RS       /* ..|x|y|y|x|64*cosT|                     */ /* SA[3] = 64*cosT */
MUL      /* ..|x|y|y|64*x*cosT|                     */
SWAP     /* ..|x|y|64*x*cosT|y|                     */
PUSHB_1  /* ..|x|y|64*x*cosT|y|2|                   */
 2
RS       /* ..|x|y|64*x*cosT|y|64*sinT|             */ /* SA[2] = 64*sinT */
MUL      /* ..|x|y|64*x*cosT|64*y*sinT|             */
SUB      /* ..|x|y|64*(x*cosT - y*sinT)|            */
PUSHW_1  /* ..|x|y|64*(x*cosT - y*sinT)|64.0|       */
 4096
DIV      /* ..|x|y|x*cosT - y*sinT|                 */
ROLL     /* ..|y|x*cosT - y*sinT|x|                 */
ROLL     /* ..|x*cosT - y*sinT|x|y|                 */
PUSHB_1  /* ..|x*cosT - y*sinT|x|y|3|               */
 3
RS       /* ..|x*cosT - y*sinT|x|y|64*cosT|         */ /* SA[3] = 64*cosT */
MUL      /* ..|x*cosT - y*sinT|x|64*y*cosT|         */
SWAP     /* ..|x*cosT - y*sinT|64*y*cosT|x|         */
PUSHB_1  /* ..|x*cosT - y*sinT|64*y*cosT|x|2|       */
 2
RS       /* ..|x*cosT - y*sinT|64*y*cosT|x|64*sinT| */ /* SA[2] = 64*sinT */
MUL      /* ..|x*cosT - y*sinT|64*y*cosT|64*x*sinT| */
ADD      /* ..|x*cosT - y*sinT|64*(x*sinT + y*cosT)| */
PUSHW_1  /* ..|x*cosT - y*sinT|64*(x*sinT + y*cosT)|64.0| */
 4096
DIV      /* ..|x*cosT - y*sinT|x*sinT + y*cosT|     */
SWAP     /* ..|x*sinT + y*cosT|x*cosT - y*sinT|     */ 
ENDF                        /* ↑関数呼び出し時とx, yの順番を合わせている */

/* Function 2: moves a control point k to the rotated coordinates         */
/* initial stack ..| k |       k: 編集する制御点番号                           */
/* final stack   ..|                                                      */
PUSHB_1
 2
FDEF     /* ..|k| */
DUP      /* ..|k|k| */
DUP      /* ..|k|k|k| */
SVTCA[x-axis]                                  /* X座標に設定 */
GC[cur]  /* ..|k|k|x_k| */
SWAP     /* ..|k|x_k|k| */
SVTCA[y-axis]                                  /* Y座標に設定 */
GC[cur]  /* ..|k|x_k|y_k| */
PUSHB_1  /* ..|k|x_k|y_k|1| */
 1
RS       /* ..|k|x_k|y_k|y_c|           */ /* y_c = SA[1]: 回転中心Y座標 */
SUB      /* ..|k|x_k|y_k-y_c|           */
SWAP     /* ..|k|y_k-y_c|x_k|           */
PUSHB_1  /* ..|k|y_k-y_c|x_k|0|         */
 0
RS       /* ..|k|y_k-y_c|x_k|x_c|       */ /* x_c = SA[0]: 回転中心X座標 */
SUB      /* ..|k|y_k-y_c|x_k-x_c|       */
PUSHB_1  /* ..|k|y_k-y_c|x_k-x_c|1|*/
 1
CALL     /* ..|k|y_k-y_c|x_k-x_c|       */ /* call function 1 */
             /* y_k' = y_k-y_c, x_k' = x_k-x_c are changed to y_k'', x_k'' */
PUSHB_1  /* ..|k|y_k''|x_k''|0|         */ 
 0
RS       /* ..|k|y_k''|x_k''|x_c|       */ /* x_c = SA[0]: 回転中心X座標 */
ADD      /* ..|k|y_k''|x_k''+x_c|       */
SWAP     /* ..|k|x_k''+x_c|y_k''|       */
PUSHB_1  /* ..|k|x_k''+x_c|y_k''|1|     */
 1
RS       /* ..|k|x_k''+x_c|y_k''|y_c|   */ /* y_c = SA[1]: 回転中心Y座標 */
ADD      /* ..|k|x_k''+x_c|y_k''+y_c|   */
SVTCA[y-axis]                                 /* Y座標に設定 */
PUSHB_1  /* ..|k|x_k''+x_c|y_k''+y_c|3| */ 
 3
CINDEX   /* ..|k|x_k''+x_c|y_k''+y_c|k| * stack先頭から3番目をトップにコピー*/
SWAP     /* ..|k|x_k''+x_c|k|y_k''+y_c| */
SCFS     /* ..|k|x_k''+x_c|            *//* 制御点kのY座標を y_k''+y_c に */
SVTCA[x-axis]                                 /* X座標に設定 */
SCFS     /* ..|                        *//* 制御点kのX座標を x_k''+x_c に */
ENDF


PUSHB_1
 3
FDEF      /* ..|n-1|           */
DUP       /* ..|n-1|n-1|       */
PUSHB_1   /* ..|n-1|n-1|0|     */
 0
CALL      /* ..|n-1|           */ /* call function 0 */
DUP       /* ..|n-1|i|         */ /* i = n-1 とおく */ 
                                  /* jump_to_here */
DUP       /* ..|n-1|i|i|       */
PUSHB_1   /* ..|n-1|i|i|0|     */
 0
GTEQ      /* ..|n-1|i|i<0|     */
IF        /* ..|n-1|i|         */ /* if i<0 then */
  DUP     /* ..|n-1|i|i|       */
  PUSHB_1 /* ..|n-1|i|i|2|     */
   2
  CALL    /* ..|n-1|i|         */ /* call function 2 */ 
  PUSHB_1 /* ..|n-1|i|1|       */ 
   1
  SUB     /* ..|n-1|i-1|       */
  PUSHW_1 /* ..|n-1|i-1|-15|   */
    -15
  JMPR    /* ..|n-1|i-1|       */ /* goto jump_to_here */
EIF                               /* end if */
POP       /* ..|n-1|           */
POP       /* ..|               */
ENDF

/* function 4 は省略、前回の記事参照 */

ソース

'fpgm'

PUSHB_1
 0
FDEF
PUSHB_1
 2
ADD
SVTCA[x-axis]
GC[orig]
PUSHB_1
 128
DIV
PUSHB_1
 0
SWAP
WS
PUSHB_1
 1
RCVT
PUSHB_1
 1
SWAP
WS
ENDF
PUSHB_1
 1
FDEF
DUP
ROLL
DUP
ROLL
PUSHB_1
 3
RS
MUL
SWAP
PUSHB_1
 2
RS
MUL
SUB
PUSHW_1
 4096
DIV
ROLL
ROLL
PUSHB_1
 3
RS
MUL
SWAP
PUSHB_1
 2
RS
MUL
ADD
PUSHW_1
 4096
DIV
SWAP
ENDF
PUSHB_1
 2
FDEF
DUP
DUP
SVTCA[x-axis]
GC[cur]
SWAP
SVTCA[y-axis]
GC[cur]
PUSHB_1
 1
RS
SUB
SWAP
PUSHB_1
 0
RS
SUB
PUSHB_1
 1
CALL
PUSHB_1
 0
RS
ADD
SWAP
PUSHB_1
 1
RS
ADD
SVTCA[y-axis]
PUSHB_1
 3
CINDEX
SWAP
SCFS
SVTCA[x-axis]
SCFS
ENDF
PUSHB_1
 3
FDEF
DUP
PUSHB_1
 0
CALL
DUP
DUP
PUSHB_1
 0
GTEQ
IF
DUP
PUSHB_1
 2
CALL
PUSHB_1
 1
SUB
PUSHW_1
 -15
JMPR
EIF
POP
POP
ENDF
PUSHB_1
 4
FDEF
DUP
PUSHW_1
 23040
GTEQ
IF
PUSHW_1
 23040
SUB
PUSHW_1
 -13
JMPR
EIF
DUP
PUSHB_1
 0
LT
IF
PUSHW_1
 23040
ADD
PUSHW_1
 -12
JMPR
EIF
PUSHB_2
 64
 64
ROLL
DUP
PUSHW_1
 11520
GT
IF
PUSHW_1
 23040
SWAP
SUB
ROLL
NEG
ROLL
ROLL
EIF
DUP
PUSHW_1
 5760
GT
IF
PUSHW_1
 11520
SWAP
SUB
SWAP
NEG
SWAP
EIF
DUP
PUSHB_1
 0
EQ
IF
POP
PUSHW_2
 0
 4096
ELSE
DUP
PUSHW_1
 5760
EQ
IF
POP
PUSHW_2
 4096
 0
ELSE
NPUSHW
 10
 57
 114
 229
 458
 916
 1833
 3666
 7331
 14648
 29184
PUSHW_4
 8340
 3
 16384
 4096
MUL
MUL
ADD
PUSHW_4
 10506
 6
 16384
 4096
MUL
MUL
ADD
PUSHW_4
 4096
 11
 16384
 4096
MUL
MUL
ADD
PUSHB_1
 14
MINDEX
PUSHW_1
 4096
MUL
PUSHW_2
 4096
 4096
PUSHB_1
 0
PUSHB_1
 64
PUSHB_1
 6
MINDEX
ROLL
PUSHB_1
 1
ADD
ROLL
PUSHB_1
 128
MUL
ROLL
PUSHB_1
 3
CINDEX
PUSHB_1
 12
LTEQ
IF
PUSHB_1
 5
MINDEX
PUSHB_1
 5
MINDEX
DUP
PUSHB_1
 5
CINDEX
DIV
PUSHB_1
 3
CINDEX
PUSHB_1
 6
CINDEX
DIV
ROLL
SWAP
PUSHB_1
 5
CINDEX
PUSHB_1
 9
CINDEX
LT
IF
ADD
ROLL
ROLL
SUB
SWAP
PUSHB_1
 5
MINDEX
PUSHB_1
 5
MINDEX
PUSHB_1
 5
MINDEX
PUSHB_1
 7
MINDEX
ADD
ELSE
SUB
ROLL
ROLL
ADD
SWAP
PUSHB_1
 5
MINDEX
PUSHB_1
 5
MINDEX
PUSHB_1
 5
MINDEX
PUSHB_1
 7
MINDEX
SUB
EIF
PUSHW_1
 -85
JMPR
EIF
POP
POP
POP
PUSHW_1
 6745
DUP
ROLL
PUSHW_1
 4096
MUL
SWAP
DIV
ROLL
PUSHW_1
 4096
MUL
ROLL
DIV
ROLL
POP
EIF
EIF
ROLL
MUL
ROLL
ROLL
MUL
SWAP
ENDF

'prep'

MPPEM
PUSHW_1
 4096
MUL
PUSHB_1
 4
CALL
PUSHB_2
 2
 3
ROLL
WS
SWAP
WS

'cvt '

index val
0 0
1 379

グリフ固有のプログラム

PUSHB_2
 11
 3
CALL

11の部分にはそのグリフの最大の制御点番号が入る。

TrueType命令で三角関数(sin, cos)を計算する

TrueType Instructionで遊ぶシリーズ。
初めてのTrueType命令: Windowsでは見えないフォントをつくる - にせねこメモ
フォントサイズに合わせて回転するフォントを作る(1) - にせねこメモ
PPEM・ポイントサイズを表示するフォント - にせねこメモ


TrueType Instructionを使って回転を計算したい。そうなると当然三角関数が使いたくなる。一方でTrueType Instruction Setには三角関数を計算する命令は入っていない。そのため自前で計算する必要がある。

方針

さて、自前で計算するにはどうすればいいだろうか?方法はいくつかある。
一つはMaclaurin展開(あるいはTaylor展開)によって計算するもの(たぶんこっちのが実装は楽)。
一つはCORDICというアルゴリズム(計算方法について調べてるときに教えてもらった)。
どちらも四則演算のみによって計算できる。


今回はCORDICというアルゴリズムを使ってみる。これは非負整数 i \in \mathbb{N}_{0}について \tan\theta_i = \frac{1}{2^i}となるような \theta_i=\mathrm{atan}\left(\frac{1}{2^i}\right)の値を事前にテーブルとして持っておいて、その値に四則演算をしていくことで、最終的な関数を得るというものらしい*1。テーブルとして持つ iの上限は必要とする精度によって決める。詳しくは次のサイトを参考にされたい。

関数電卓コラム 10/02/24 関数電卓のしくみ(CORDICアルゴルズム)

F26Dot6の精度

さて、TrueType命令で使える小数フォーマットはF26Dot6(以降Q6とも書く)、すなわち整数部が26ビットで小数部が6ビットである固定小数点数である。
Q6の値は整数と同様の扱いであるが、 2^6 = 64だけ下駄がはかされていると考えるとよい。すなわち、Q6を整数として解釈した数値を64で割ると実際の数値が求まる。
例えばQ6フォーマットで表される数値を整数で表現すると、Q6の1.0は整数の64、Q6の2.0は整数の128、Q6の0.75は整数の48、などとなる。

TrueType命令において、足し算(ADD[ ])・引き算(SUB[ ])は整数に対して定義されている。これは小数点の位置を変化させないため固定小数点数に対してもそのまま使える。
一方で掛け算(MUL[ ])や割り算(DIV[ ])はF26Dot6に対して定義されていて、計算結果もF26Dot6である。これは、実際には次のような計算が行われるとのことである。

A * B: (n1 * n2) / 64
A / B: (n1 * 64) / n2

n1, n2は整数として扱われ、基本的に整数同士の掛け算あるいは割り算と同様であるが、固定小数点数なので右端(LSB)から6ビットが小数部であることを考慮して、最終的な計算結果がF26Dot6となるように、適当なところで64で割ったり64を掛けたりして桁を調整している。64で割るのは6ビット分算術右シフトすることと同等で、64掛けるのは6bit分左シフトするのと同等であるが、TrueType命令にビットシフト演算はない。

ところで、A*Bの場合は計算結果を64で割っていて、A/Bの場合はAに64を掛けている。仕様に記載があるかもしれないし、または実装に依るのかもしれないが、32bit整数型で計算していた場合、A*Bの先頭6bitは桁あふれによって、またA/BにおけるAの先頭6ビットのデータについてもデータが失われた状態になりそうな気がする。そう考えると、乗除算で実質に使えるのは26ビット程度(20Dot6、つまり-524288.~524287.程度)になるかもしれない。大きな数値を扱う必要がある場合には問題になる可能性があるかもしれない。


それにしても、フォントの制御点を表すためとはいえ、小数部が6bitというのはかなり心もとない気がする。 2^6 = 0.015625であるから、10進数で0.01の細かさは表現できないことになる。さすがに \sin \cosを計算するのにこれだと心許ない気がするので、さらに6bit分下駄をはかせて20Dot12 (Q12)相当として計算していくことにする。乗除算を計算する際には上で示したようにF26Dot6用で計算されるため、出力される数値の小数点の位置を適宜調整する必要がでてくることに気を付けないといけない。

CORDICアルゴリズムの実装

定数のストア

まず使用する定数類をStorage Areaに保存して、プログラム内から読み出すことを考えてみる。
Storage Areaへの割り付けは次のようにする。定数には6ビット分(=64)だけ下駄をはかせている。

index value 備考
0 0x2D000  64\theta_0 = \mathrm{atan}\left(\frac{1}{2^0}\right)
1 0x1A90A  64\theta_1 = \mathrm{atan}\left(\frac{1}{2^1}\right)
2 0xE094  64\theta_2 = \mathrm{atan}\left(\frac{1}{2^2}\right)
3 0x7200  64\theta_3 = \mathrm{atan}\left(\frac{1}{2^3}\right)
4 0x3938  64\theta_4 = \mathrm{atan}\left(\frac{1}{2^4}\right)
5 0x1CA3  64\theta_5 = \mathrm{atan}\left(\frac{1}{2^5}\right)
6 0x0E52  64\theta_6 = \mathrm{atan}\left(\frac{1}{2^6}\right)
7 0x0729  64\theta_7 = \mathrm{atan}\left(\frac{1}{2^7}\right)
8 0x0394  64\theta_8 = \mathrm{atan}\left(\frac{1}{2^8}\right)
9 0x01CA  64\theta_9 = \mathrm{atan}\left(\frac{1}{2^9}\right)
10 0x00E5  64\theta_{10} = \mathrm{atan}\left(\frac{1}{2^{10}}\right)
11 0x0072  64\theta_{11} = \mathrm{atan}\left(\frac{1}{2^{11}}\right)
12 0x0039  64\theta_{12} = \mathrm{atan}\left(\frac{1}{2^{12}}\right)
13 0x1A59  r_{12}

定数なので'prep'でstoreしてしまおう。

ここで、PUSHWでは1ワード(16 bit)が符号拡張されるため、すなわち1ワードの最上位ビットが符号ビットとなるため、0x8000以上の数値はそのままPUSHWを使ってスタックに積むことはできない。例えば、

(上14ビット)*2^14 + (下14ビット)

みたいに値を分割してPUSHして後で合成してやらないといけない。

'prep'
PUSHB_1 /* store dest */
0
PUSHW_4
0x1000  /* 0x2D000 % 2^14 */
0x000B  /* 0x2D000 / 2^14 */
0x4000  /* 0x100.0 */
0x1000  /* 0x40.0 */
MUL
MUL
ADD
WS

PUSHB_1 /* store dest */
1
PUSHW_4
0x290A  /* 0x1A90A % 2^14 */
0x0006  /* 0x1A90A / 2^14 */
0x4000  /* 0x100.0 */
0x1000  /* 0x40.0 */
MUL
MUL
ADD
WS


PUSHB_1 /* store dest */
2
PUSHW_4
0x2094  /* 0xE094 % 2^14 */
0x0003  /* 0xE094 / 2^14 */
0x4000  /* 0x100.0 */
0x1000  /* 0x40.0 */
MUL
MUL
ADD
WS

PUSHB_1 /* store dest */
3
PUSHW_1
0x7200
WS

PUSHB_1 /* store dest */
4
PUSHW_1
0x3938
WS

PUSHB_1 /* store dest */
5
PUSHW_1
0x1CA3
WS

PUSHB_1 /* store dest */
6
PUSHW_1
0x0E52
WS

PUSHB_1 /* store dest */
7
PUSHW_1
0x0729
WS

PUSHB_1 /* store dest */
8
PUSHW_1
0x0394
WS

PUSHB_1 /* store dest */
9
PUSHW_1
0x01CA
WS

PUSHB_1 /* store dest */
10
PUSHW_1
0x00E5
WS

PUSHB_1 /* store dest */
11
PUSHW_1
0x0072
WS

PUSHB_1 /* store dest */
12
PUSHW_1
0x0039
WS

PUSHB_1 /* store dest */
13
PUSHW_1
0x1A59
WS

計算の実装

さて、CORDICアルゴリズムで実際に計算をする部分の実装に入る。
入力は角度{\theta[\mbox{°}]}(F26Dot6)、出力は 64\cdot\sin\theta, 64\cdot\cos\theta(F26Dot6)とする。
入力となる角度{\theta[\mbox{°}]} 0 \le \theta \le 90の範囲にあるものとする。

Pythonで書くとこんな感じになる。

import math

# constants 定数格納部分
theta = [math.atan(1.0 / (2**x)) * 180 / math.pi for x in range(13)]
rsq = [2.0]
for i in range(1,13):
    rsq.append(rsq[-1] + rsq[-1]/(2.0**(2*i)))
r = list(map(math.sqrt, rsq))

# calculate sin, cos 計算部分
deg = 30 #input angle 入力の角度 [0,90]
d = theta[0] # current temporary angle
x = 1.0
y = 1.0
for i in range(1,13):
    lastx = x
    lasty = y
    if d > deg:
        d = d - theta[i]
        x = lastx + lasty/(2**i)
        y = lasty - lastx/(2**i)
    else:
        d = d + theta[i]
        x = lastx - lasty/(2**i)
        y = lasty + lastx/(2**i)

mycos = x/r[12] #cos計算結果
mysin = y/r[12] #sin計算結果

#print("mycos:", mycos, ", mysin:", mysin)
#print("  cos:", math.cos(deg/180*math.pi), ",   sin:", math.sin(deg/180*math.pi))

定数格納はしたので、計算部分を実装する。

最初に入力 \thetaを与えて、それに対応する \sin\theta, \cos\thetaを計算するような次のプログラムをつくった。

PUSHW_1 1920 /* theta=30.0 */ /* input angle */



             /* ...|theta| */
PUSHW_1 4096 /* ...|theta|64.0| */
MUL          /* ...|64t| */ /* 64t = 64*theta, 更に6ビット下駄を履かす */

PUSHW_2      /* ...|64t|64*1.0|64*1.0| */ 
 4096 
 4096 
PUSHB_1 0    /* ...|64t|64*1.0|64*1.0|0| */
DUP          /* ...|64t|64*1.0|64*1.0|0|0| */
RS           /* ...|64t|64*1.0|64*1.0|0|64t0| *//*t0=theta_0=StorageArea[0]*/
PUSHB_1 64   /* ...|64t|64*1.0|64*1.0|0|64t0|1.0| */
SWAP         /* ...|64t|64*1.0|64*1.0|0|1.0|64t0| */
             /* ...|64t|64x=64*1.0|64y=64*1.0|i=0|2.0^i=1.0|ti=64t0| */
             /* i: counter,   2.0^i: 冪関数やビットシフト演算の代替*/
             /* ti: current angle */
/* L0: */
/* i = i+1, 2.0^i = 2.0^(i+1) に更新 */  
ROLL         /* ...|64t|64x|64y|2.0^i|64ti|i| */ 
PUSHB_1 1    /* ...|64t|64x|64y|2.0^i|64ti|i|1| */
ADD          /* ...|64t|64x|64y|2.0^i|64ti|i+1| */
ROLL         /* ...|64t|64x|64y|64ti|i+1|2.0^i| */
PUSHB_1 128  /* ...|64t|64x|64y|64ti|i+1|2.0^i|2.0| */
MUL          /* ...|64t|64x|64y|64ti|i+1|2.0^(i+1)| */
ROLL         /* ...|64t|64x|64y|i+1|2.0^(i+1)|64ti| */

/* if i <= 12  */
PUSHB_1 3
CINDEX       /* ...|64t|64x|64y|i|2.0^i|64ti|i| */
PUSHB_1 12   /* ...|64t|64x|64y|i|2.0^i|64ti|i|12| */
LTEQ         /* ...|64t|64x|64y|i|2.0^i|64ti|i<=12| */
IF           /* ...|64t|64x|64y|i|2.0^i|64ti| */ /* if (i <= 12) */
  
  PUSHB_1 5
  MINDEX     /* ...|64t|64y|i|2.0^i|64ti|64x| */
  PUSHB_1 5
  MINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y| */
  DUP        /* ...|64t|i|2.0^i|64ti|64x|64y|64y| */
  PUSHB_1 5
  CINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y|64y|2.0^i| */
  DIV        /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i| */
  PUSHB_1 3
  CINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x| */
  PUSHB_1 6
  CINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x|2.0^i| */
  DIV        /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x/2.0^i| */

  ROLL       /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64x/2.0^i|64y| */
  SWAP       /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i| */

  /* if (ti<t) { */
  PUSHB_1 5
  CINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti| */
  PUSHB_1 9
  CINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti|64t| */
  LT         /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti<64t| */
  IF         /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i| */ 
    /* if ti<t */

    /* x -= y/(2^i) , y+= x/(2^i)*/
    ADD      /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y+64x/2.0^i| */
    ROLL     /* ...|64t|i|2.0^i|64ti|64y/2.0^i|64y+64x/2.0^i|64x| */
    ROLL     /* ...|64t|i|2.0^i|64ti|64y+64x/2.0^i|64x|64y/2.0^i| */
    SUB      /* ...|64t|i|2.0^i|64ti|64y+64x/2.0^i|64x-64y/2.0^i| */
    SWAP     /* ...|64t|i|2.0^i|64ti|64x-64y/2.0^i|64y+64x/2.0^i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|2.0^i|64ti|64x-64y/2.0^i|64y+64x/2.0^i|i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|64ti|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti| */
    
    /* ti = ti+thetai */
    PUSHB_1 3
    CINDEX   /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti|i| */
    RS       /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti|theta_i| */
    ADD      /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti+theta_i| */ 

  ELSE /* if it>= t */

    /* x += y/(2^i) , y-= x/(2^i)*/
    SUB      /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y-64x/2.0^i| */
    ROLL     /* ...|64t|i|2.0^i|64ti|64y/2.0^i|64y-64x/2.0^i|64x| */
    ROLL     /* ...|64t|i|2.0^i|64ti|64y-64x/2.0^i|64x|64y/2.0^i| */
    ADD      /* ...|64t|i|2.0^i|64ti|64y-64x/2.0^i|64x+64y/2.0^i| */
    SWAP     /* ...|64t|i|2.0^i|64ti|64x+64y/2.0^i|64y-64x/2.0^i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|2.0^i|64ti|64x+64y/2.0^i|64y-64x/2.0^i|i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|64ti|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti| */

    /* ti = ti-theta_i */
    PUSHB_1 3
    CINDEX   /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti|i| */
    RS       /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti|theta_i| */
    SUB      /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti-theta_i| */ 

  EIF
  PUSHW_1 -87
  JMPR       /*goto L0*/
EIF
             /* ...|64t|64x|64y|i|2.0^i|64ti| */
POP          /* ...|64t|64x|64y|i|2.0^i| */
POP          /* ...|64t|64x|64y|i| */
POP          /* ...|64t|64x|64y| */
PUSHB_1 13   /* ...|64t|64x|64y|13| */
RS           /* ...|64t|64x|64y|64r_12| */ /* 64r_12=StorageArea[13] */
DUP          /* ...|64t|64x|64y|64r_12|64r_12| */
ROLL         /* ...|64t|64x|64r_12|64r_12|64y| */
PUSHW_1 4096 /* ...|64t|64x|64r_12|64r_12|64y|64.0| */
MUL          /* ...|64t|64x|64r_12|64r_12|64*64y| */
SWAP         /* ...|64t|64x|64r_12|64*64y|64r_12| */
DIV          /* ...|64t|64x|64r_12|64sin| */ /* 64sin = 64*y/r_12 */
ROLL         /* ...|64t|64r_12|64sin|64x| */
PUSHW_1 4096 /* ...|64t|64r_12|64sin|64x|64.0| */
MUL          /* ...|64t|64r_12|64sin|64*64x| */
ROLL         /* ...|64t|64sin|64*64x|64r_12| */
DIV          /* ...|64t|64sin|64cos| */ /* 64cos = 64*x/r_12 */
ROLL         /* ...|64sin|64cos|64t| */
POP          /* ...|64sin|64cos| */

やっぱStorage Area使わない方がいいよね

Storage Area使うと、他の関数や命令で使う値を潰してしまうかもしれないので、あまりよろしくない。という訳で、Storage Area使わない版もつくった。

theta_iの定数には、順番に、かつ1回しかアクセスしないため、スタックの下の方に定数を先に使う方が上になるように積んでおいて(theta_12, theta_11, ..., theta_1, theta_0)、MINDEXで若い順にスタックトップに取り出して使うようにした。
MINDEXはスタックトップから数えるので、ループでも、毎回同じ番号を指定することで定数に順番にアクセスできる。

PUSHW_1 1920 /* 30 deg */ /*入力として与える角度*/


/* stack: ...|theta| */
/* PUSH constants */
NPUSHW 10
 0x0039
 0x0072
 0x00E5
 0x01CA
 0x0394
 0x0729
 0x0E52
 0x1CA3
 0x3938
 0x7200
PUSHW_4
 0x2094  /* 0xE094 % 2^14 */
 0x0003  /* 0xE094 / 2^14 */
 0x4000  /* 0x100.0 */
 0x1000  /* 0x40.0 */
MUL
MUL
ADD
PUSHW_4
 0x290A  /* 0x1A90A % 2^14 */
 0x0006  /* 0x1A90A / 2^14 */
 0x4000  /* 0x100.0 */
 0x1000  /* 0x40.0 */
MUL
MUL
ADD
PUSHW_4
 0x1000  /* 0x2D000 % 2^14 */
 0x000B  /* 0x2D000 / 2^14 */
 0x4000  /* 0x100.0 */
 0x1000  /* 0x40.0 */
MUL
MUL
ADD
             /* ...|theta|theta_12|theta_11|...|theta_0| */


PUSHB_1 14
MINDEX       /* ...|64theta_12|...|64theta_0|theta| */
PUSHW_1 4096 /* ...|64theta_12|...|64theta_0|theta|64.0| */
MUL          /* ...|64theta_12|...|64theta_0|64t| */ /* 64t=64*theta */
             /* ...|64theta_i|64t| */

PUSHW_2      /* ...|64theta_0|64t|64*1.0|64*1.0| */
 4096
 4096 
PUSHB_1 0
PUSHB_1 64   /* ...|64theta_0|64t|64*1.0|64*1.0|i=0|1.0| */
PUSHB_1 6  
MINDEX       /* ...|64theta_1|64t|64*1.0|64*1.0|i=0|1.0|64theta_0| */ 
   /* ...|64theta_i|64t|64x=64*1.0|64y=64*1.0|i=0|2.0^i=1.0|64ti=64theta_0| */

/*L0:*/ 
/* i = i+1, 2.0^i = 2.0^(i+1) に更新 */  
             /* ...|64t|64x|64y|i|2.0^i|64ti| */ 
ROLL         /* ...|64t|64x|64y|2.0^i|64ti|i| */ /* i++ */
PUSHB_1 1    /* ...|64t|64x|64y|2.0^i|64ti|i|1| */
ADD          /* ...|64t|64x|64y|2.0^i|64ti|i+1| */
ROLL         /* ...|64t|64x|64y|64ti|i+1|2.0^i| */
PUSHB_1 128  /* ...|64t|64x|64y|64ti|i+1|2.0^i|2.0| */
MUL          /* ...|64t|64x|64y|64ti|i+1|2.0^(i+1)| */
ROLL         /* ...|64t|64x|64y|i+1|2.0^(i+1)|64ti| */

/* if i <= 12  */
PUSHB_1 3
CINDEX       /* ...|64t|64x|64y|i|2.0^i|64ti|i| */
PUSHB_1 12   /* ...|64t|64x|64y|i|2.0^i|64ti|i|12| */
LTEQ         /* ...|64t|64x|64y|i|2.0^i|64ti|i<=12| */
IF           /* ...|64t|64x|64y|i|2.0^i|64ti| */ /* if (i <= 12) */

  PUSHB_1 5
  MINDEX     /* ...|64t|64y|i|2.0^i|64ti|64x| */
  PUSHB_1 5
  MINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y| */
  DUP        /* ...|64t|i|2.0^i|64ti|64x|64y|64y| */
  PUSHB_1 5
  CINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y|64y|2.0^i| */
  DIV        /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i| */
  PUSHB_1 3
  CINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x| */
  PUSHB_1 6
  CINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x|2.0^i| */
  DIV        /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x/2.0^i| */

  ROLL       /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64x/2.0^i|64y| */
  SWAP       /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i| */

  /* if (ti<t) { */
  PUSHB_1 5
  CINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti| */
  PUSHB_1 9
  CINDEX     /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti|64t| */
  LT         /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti<64t| */
  IF         /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i| */ 
    /* if ti<t */

    /* x -= y/(2^i) , y+= x/(2^i)*/
    ADD      /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y+64x/2.0^i| */
    ROLL     /* ...|64t|i|2.0^i|64ti|64y/2.0^i|64y+64x/2.0^i|64x| */
    ROLL     /* ...|64t|i|2.0^i|64ti|64y+64x/2.0^i|64x|64y/2.0^i| */
    SUB      /* ...|64t|i|2.0^i|64ti|64y+64x/2.0^i|64x-64y/2.0^i| */
    SWAP     /* ...|64t|i|2.0^i|64ti|64x-64y/2.0^i|64y+64x/2.0^i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|2.0^i|64ti|64x-64y/2.0^i|64y+64x/2.0^i|i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|64ti|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti| */

    /* ti = ti+theta_i */
    PUSHB_1 7
    MINDEX   /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti|theta_i| */
    ADD      /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti+theta_i| */ 

  ELSE /* if it>= t */

    /* x += y/(2^i) , y-= x/(2^i)*/
    SUB      /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y-64x/2.0^i| */
    ROLL     /* ...|64t|i|2.0^i|64ti|64y/2.0^i|64y-64x/2.0^i|64x| */
    ROLL     /* ...|64t|i|2.0^i|64ti|64y-64x/2.0^i|64x|64y/2.0^i| */
    ADD      /* ...|64t|i|2.0^i|64ti|64y-64x/2.0^i|64x+64y/2.0^i| */
    SWAP     /* ...|64t|i|2.0^i|64ti|64x+64y/2.0^i|64y-64x/2.0^i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|2.0^i|64ti|64x+64y/2.0^i|64y-64x/2.0^i|i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|64ti|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i| */
    PUSHB_1 5
    MINDEX   /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti| */

    /* ti = ti-theta_i */
    PUSHB_1 7
    MINDEX   /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti|theta_i| */
    SUB      /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti-theta_i| */ 

  EIF
  PUSHW_1 -85
  JMPR  /*goto L0*/
EIF
             /* ...|64t|64x|64y|i|2.0^i|64ti| */
POP          /* ...|64t|64x|64y|i|2.0^i| */
POP          /* ...|64t|64x|64y|i| */
POP          /* ...|64t|64x|64y| */
PUSHW_1      /* ...|64t|64x|64y|64r_12| */ /* 64*r12 */
 0x1A59 
DUP          /* ...|64t|64x|64y|64r_12|64r_12| */
ROLL         /* ...|64t|64x|64r_12|64r_12|64y| */
PUSHW_1 4096 /* 64.0 */
MUL          /* ...|64t|64x|64r_12|64r_12|64*64y| */
SWAP         /* ...|64t|64x|64r_12|64*64y|64r_12| */
DIV          /* ...|64t|64x|64r_12|64sin| */ /* 64sin = 64*y/r_12 */
ROLL         /* ...|64t|64r_12|64sin|64x| */
PUSHW_1 4096 /* 64.0 */
MUL          /* ...|64t|64r_12|64sin|64*64x| */
ROLL         /* ...|64t|64sin|64*64x|64r_12| */
DIV          /* ...|64t|64sin|64cos| */ /* 64cos = 64*x/r_12 */
ROLL         /* ...|64sin|64cos|64t| */
POP          /* ...|64sin|64cos| */

計算の定義域の拡大

さて、0~90度の範囲だけしか計算できないというのはよろしくないので、これ以外の範囲にも対応できるようにする。


最初に、\theta 0\le \theta < 360の範囲に入るようにする。
これは、三角関数 2\piの周期関数であることから、自然数nについて
 \sin(\theta) = \sin(\theta-2n\pi)
 \cos(\theta) = \cos(\theta-2n\pi)
が成り立つことによる。つまり、\theta

  • 360以上 → 360未満になるまで360を引く
  • 0未満 → 0以上になるまで360を足す
         /* stack: ...|theta(F26Dot6)| */

/* L1: */
DUP             /* ...|theta|theta| */ 
PUSHW_1 23040   /* ...|theta|theta|360.0| */
GTEQ            /* ...|theta|theta>=360.0| */
IF              /* ...|theta| */ /* if (theta >= 360.0) */
  PUSHW_1 23040 /* ...|theta|360.0| */
  SUB           /* ...|theta-360.0| */
  PUSHW_1 -13   /* ...|theta-360.0|-13| */
  JMPR          /* ...|theta-360.0| */ /* goto L1 */
EIF

/* L2: */
DUP             /* ...|theta|theta| */ 
PUSHB_1 0       /* ...|theta|theta|0.0| */
LT              /* ...|theta|theta<0.0| */
IF              /* ...|theta| */ /* if (theta < 0.0) */
  PUSHW_1 23040 /* ...|theta|360.0| */
  ADD           /* ...|theta+360.0| */
  PUSHW_1 -12   /* ...|theta+360.0|-12| */
  JMPR          /* ...|theta+360.0| */ /* goto L2 */
EIF

PUSHB_2 64 64 /* ...|theta|sign_sin=1.0|sign_cos=1.0| */
ROLL /* ...|sign_sin|sign_cos|theta| */

最後でsinとcosに対応する符号(1.0で初期化)をスタックに用意した。これは次で利用する。


次に、 180 < \theta < 360の場合に対応する。
 \sin\theta = -\sin(2\pi-\theta)
 \cos\theta = \cos(2\pi-\theta)
となることより、 180 < \theta < 360の場合は、

  •  \theta := 360 - \theta
  • sinの符号を反転
DUP /* ...|sign_sin|sign_cos|theta|theta| */
PUSHW_1 11520 /* ...|sign_sin|sign_cos|theta|theta|180.0| */
GT  /* ...|sign_sin|sign_cos|theta|theta>180.0| */
IF  /* ...|sign_sin|sign_cos|theta| */ /* if (theta>180.0) */
  PUSHW_1 23040 /* ...|sign_sin|sign_cos|theta|360.0| */
  SWAP  /* ...|sign_sin|sign_cos|360.0|theta| */
  SUB   /* ...|sign_sin|sign_cos|360.0-theta| */
  ROLL  /* ...|sign_cos|360.0-theta|sign_sin| */
  NEG   /* ...|sign_cos|360.0-theta|-sign_sin| */
  ROLL  /* ...|360.0-theta|-sign_sin|sign_cos| */
  ROLL  /* ...|-sign_sin|sign_cos|360.0-theta| */
EIF

次に、 90 < \theta \le 180のときに対応する。
 \sin\theta = \sin(\pi - \theta)
 \cos\theta = -\cos(\pi - \theta)
となることより、 90 < \theta \le 180となるときは、

  •  \theta := 180 - \theta
  • cosの符号を反転
DUP /* ...|sign_sin|sign_cos|theta|theta| */
PUSHW_1 5760 /* ...|sign_sin|sign_cos|theta|theta|90.0| */
GT  /* ...|sign_sin|sign_cos|theta|theta>90.0| */
IF  /* ...|sign_sin|sign_cos|theta| */ /* if (theta>90.0) */
  PUSHW_1 11520 /* ...|sign_sin|sign_cos|theta|180.0| */
  SWAP  /* ...|sign_sin|sign_cos|180.0|theta| */
  SUB   /* ...|sign_sin|sign_cos|180.0-theta| */
  SWAP  /* ...|sign_sin|180.0-theta|sign_cos| */
  NEG   /* ...|sign_sin|180.0-theta|-sign_cos| */
  SWAP  /* ...|sign_sin|-sign_cos|180.0-theta| */
EIF


0度と90度の時は場合分けして値を出すようにしてみた。まあそんなことしなくても結構な精度で計算してくれるのだけれど。
/* CORDIC algorithm comes here */ の部分は、実際には上で作ったようなCORDICアルゴリズムによる角度計算のプログラムを入れる。

DUP         /* ...|sign_sin|sign_cos|theta|theta| */
PUSHB_1 0   /* ...|sign_sin|sign_cos|theta|theta|0.0| */
EQ          /* ...|sign_sin|sign_cos|theta|theta==0.0| */
IF          /* ...|sign_sin|sign_cos|theta| */ /* if (theta == 0.0) */
  POP       /* ...|sign_sin|sign_cos| */
  PUSHW_2   /* ...|sign_sin|sign_cos|0.0|64.0| */ 
   0
   4096 
            /* ...|sign_sin|sign_cos|64sin0 = 0|64cos0 = 64*1.0| */
ELSE
  DUP       /* ...|sign_sin|sign_cos|theta|theta| */
  PUSHW_1   /* ...|sign_sin|sign_cos|theta|theta|90.0| */
   5760
  EQ        /* ...|sign_sin|sign_cos|theta|theta==90.0| */
  IF        /* ...|sign_sin|sign_cos|theta| */ /* if (theta==90.0) */
    POP     /* ...|sign_sin|sign_cos| */
    PUSHW_2 /* ...|sign_sin|sign_cos|64.0|0.0| */
     4096 
     0
            /* ...|sign_sin|sign_cos|sin90 = 64.0|cos90 = 0.0| */
  ELSE /* theta != 0.0 and theta != 90.0 */
            /* ...|sign_sin|sign_cos|theta| */

    /* CORDIC algorithm comes here */

            /* ...|sign_sin|sign_cos|64sin|64cos| */
  EIF
EIF         /* ...|sign_sin|sign_cos|64sin|64cos| */

ROLL        /* ...|sign_sin|64sin|64cos|sign_cos| */
MUL         /* ...|sign_sin|64sin|sign_cos*64cos| */
ROLL        /* ...|64sin|sign_cos*64cos|sign_sin| */
ROLL        /* ...|sign_cos*64cos|sign_sin|64sin| */
MUL         /* ...|sign_cos*64cos|sign_sin*64sin| */
SWAP        /* ...|sign_sin*64sin|sign_cos*64cos| */

関数化

さて、これらの計算を関数としてまとめる。関数0とした。

initial stack: ...|angle (deg) (F26Dot6)|
  final stack: ...|64*sin|64*cos|
PUSHB_1
 0
FDEF
DUP
PUSHW_1
 23040
GTEQ
IF
PUSHW_1
 23040
SUB
PUSHW_1
 -13
JMPR
EIF
DUP
PUSHB_1
 0
LT
IF
PUSHW_1
 23040
ADD
PUSHW_1
 -12
JMPR
EIF
PUSHB_2
 64
 64
ROLL
DUP
PUSHW_1
 11520
GT
IF
PUSHW_1
 23040
SWAP
SUB
ROLL
NEG
ROLL
ROLL
EIF
DUP
PUSHW_1
 5760
GT
IF
PUSHW_1
 11520
SWAP
SUB
SWAP
NEG
SWAP
EIF
DUP
PUSHB_1
 0
EQ
IF
POP
PUSHW_2
 0
 4096
ELSE
DUP
PUSHW_1
 5760
EQ
IF
POP
PUSHW_2
 4096
 0
ELSE
NPUSHW
 10
 57
 114
 229
 458
 916
 1833
 3666
 7331
 14648
 29184
PUSHW_4
 8340
 3
 16384
 4096
MUL
MUL
ADD
PUSHW_4
 10506
 6
 16384
 4096
MUL
MUL
ADD
PUSHW_4
 4096
 11
 16384
 4096
MUL
MUL
ADD
PUSHB_1
 14
MINDEX
PUSHW_1
 4096
MUL
PUSHW_2
 4096
 4096
PUSHB_1
 0
PUSHB_1
 64
PUSHB_1
 6
MINDEX
ROLL
PUSHB_1
 1
ADD
ROLL
PUSHB_1
 128
MUL
ROLL
PUSHB_1
 3
CINDEX
PUSHB_1
 12
LTEQ
IF
PUSHB_1
 5
MINDEX
PUSHB_1
 5
MINDEX
DUP
PUSHB_1
 5
CINDEX
DIV
PUSHB_1
 3
CINDEX
PUSHB_1
 6
CINDEX
DIV
ROLL
SWAP
PUSHB_1
 5
CINDEX
PUSHB_1
 9
CINDEX
LT
IF
ADD
ROLL
ROLL
SUB
SWAP
PUSHB_1
 5
MINDEX
PUSHB_1
 5
MINDEX
PUSHB_1
 5
MINDEX
PUSHB_1
 7
MINDEX
ADD
ELSE
SUB
ROLL
ROLL
ADD
SWAP
PUSHB_1
 5
MINDEX
PUSHB_1
 5
MINDEX
PUSHB_1
 5
MINDEX
PUSHB_1
 7
MINDEX
SUB
EIF
PUSHW_1
 -85
JMPR
EIF
POP
POP
POP
PUSHW_1
 6745
DUP
ROLL
PUSHW_1
 4096
MUL
SWAP
DIV
ROLL
PUSHW_1
 4096
MUL
ROLL
DIV
ROLL
POP
EIF
EIF
ROLL
MUL
ROLL
ROLL
MUL
SWAP
ENDF

*1:こんな風にMathJaxで数学記号が出せるのほんと楽しい。

PPEM・ポイントサイズを表示するフォント

TrueType命令で遊ぶシリーズ。
前回までの記事はこちら:

何をするのか

ラスタライザのTrueType命令回りの挙動を見るのに便利かなあと思って、TrueType命令から取得できるPPEM(EMあたりのピクセル数)やポイント数を可視化できるようなフォントを作ってみる。

完成品

PPEM

f:id:nixeneko:20170129005803p:plain

Point Size

f:id:nixeneko:20170129005837p:plain

ダウンロード

尚、この記事の末尾にTrueType命令のソースコードをまとめて記載した。

追記(2017-02-01 4時ころ)

gitlab.com
同様のものがすでにあったみたいですね。しかもこっちのが高機能だし……。

というか、PPEMは縦・横で異なる可能性があるのですね*1。長体・平体がかかった時は、確かに縦横でppemが異なりそう。

今回作成したものでは特にprojection vectorの指定をしてないので、MPPEMの前にSTVCA命令などでppemを計る軸を指定した方が良さそう。
(追記終)

方針

伝統ある7セグメント数字を使う。

一つの桁について、初めは8の字が表示されるように7つのセグメントを用意しておく。各セグメントには下図のように番号を割り振っておく。
f:id:nixeneko:20170129010346p:plain

このセグメントについて、その桁の数字が何であるかを見て、各セグメントを必要であれば適切に消去する。これによってその数字を構成するセグメントのみが残り、数字が表示できるという訳である。

セグメントNo. セグメントあり セグメントなし
0 0,2,3,5,6,7,8,9 1,4
1 0,4,5,6,8,9 1,2,3,7
2 0,1,2,3,4,7,8,9 5,6
3 2,3,4,5,6,8,9 0,1,7
4 0,2,6,8 1,3,4,5,7,9
5 0,1,3,4,5,6,7,8,9 2
6 0,2,3,5,6,8,9 1,4,7

実装

次のようなグリフを用意した。
f:id:nixeneko:20170129011619p:plain
各セグメントは4つの制御点からなり、セグメントの並び順は上図の丸囲み数字の番号が若い順に並ぶようにした。
丸囲みの数字をセグメント番号sとすると、セグメントsは制御点4s~4s+3からなる。

また、四角で囲んだ数字(mとする)は、 10^mの位の数字を表す。各桁に対応する数字位置におけるセグメントn ( 0 \le n \le 6)を考えると、
 s = 7m+n
となるようになっている。

なお、セグメントの並べ替えは、.ttfに書き出し→ttxで.ttxに変換→テキストエディタで並べ替え→.ttfに変換という風にして行った。

関数0: 指定した制御点を原点に移動

次にTrueType命令を考えていく。
さて、まずは、以前作った、指定した番号の制御点を原点に移動する関数0をもってきて使うことにする。

PUSHB_1
0
FDEF
STVCA[1]
DUP
PUSHW_1
 0x0000
SCFS
STVCA[0]
PUSHW_1
 0x0000
SCFS
ENDF

関数1: 指定したセグメントを消去

次に、指定した番号のセグメントを消去する関数1を考える。
入力は消去したいセグメント番号sとする。
セグメント番号sは、右からm番目( 0 \le m \le 3)の数字のセグメントn ( 0 \le m \le 6)について s = 7m+nで計算したものとする。
このとき、関数1は、制御点番号4s, 4s+1, 4s+2, 4s+3を原点に移動する。

/* func1: removes segment s */
/* initial stack ...|s|     */
/* final stack   ...|       */
PUSHB_1 1
FDEF        /* ...|s|                       */
PUSHW_1 256 /* ...|s|4.0|                   */
MUL         /* ...|4*s|                     */
DUP         /* ...|4*s|4*s|                 */
PUSHB_1 1   /* ...|4*s|4*s|1|               */
ADD         /* ...|4*s|4*s+1|               */
DUP         /* ...|4*s|4*s+1|4*s+1|         */
PUSHB_1 1   /* ...|4*s|4*s+1|4*s+1|1|       */
ADD         /* ...|4*s|4*s+1|4*s+2|         */
DUP         /* ...|4*s|4*s+1|4*s+2|4*s+2|   */
PUSHB_1 1   /* ...|4*s|4*s+1|4*s+2|4*s+2|1| */
ADD         /* ...|4*s|4*s+1|4*s+2|4*s+3|   */
PUSHB_1 0   /* ...|4*s|4*s+1|4*s+2|4*s+3|0| */
CALL        /* ...|4*s|4*s+1|4*s+2|         */ /*call func0 with 4*s+3*/
PUSHB_1 0   /* ...|4*s|4*s+1|4*s+2|0|       */
CALL        /* ...|4*s|4*s+1|               */ /*call func0 with 4*s+2*/
PUSHB_1 0   /* ...|4*s|4*s+1|0|             */
CALL        /* ...|4*s|                     */ /*call func0 with 4*s+1*/
PUSHB_1 0   /* ...|4*s|0|                   */
CALL        /* ...|                         */ /*call func0 with 4*s  */
ENDF

関数2: 各桁についての数字を表示

さて、次に、右からm番目( 0 \le m \le 3)の数字の値dについて適切にセグメントを消去する関数2を考える。これはまあ大体気合で。

/* func2: removes some segments of the m-th digit to show the number d */
/* initial stack ...|d|m| */
/* final stack   ...|     */
PUSHB_1 2
FDEF         /* ...|d|m|           */

PUSHW_1 448  /* ...|d|m|7.0|       */
MUL          /* ...|d|7*m|         */
SWAP         /* ...|7*m|d|         */

DUP          /* ...|7*m|d|d|       */
PUSHB_1 0    /* ...|7*m|d|d|0|     */
EQ           /* ...|7*m|d|d==0|    */
IF           /* ...|7*m|d|         */ /* if d==0 */ /* 消去: 3 */
  SWAP       /* ...|d|7*m|         */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 3  /* ...|d|7*m|7*m|3|   */
  ADD        /* ...|d|7*m|7*m+3|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+3|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+3 */
  SWAP       /* ...|7*m|d|         */
EIF          /* ...|7*m|d|         */

DUP          /* ...|7*m|d|d|       */
PUSHB_1 1    /* ...|7*m|d|d|1|     */
EQ           /* ...|7*m|d|d==1|    */
IF           /* ...|7*m|d|         */ /* if d==1 */ /* 消去: 0,1,3,4,6 */
  SWAP       /* ...|d|7*m|         */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 1  /* ...|d|7*m|7*m|1|   */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 1  /* ...|d|7*m|7*m|1|   */
  ADD        /* ...|d|7*m|7*m+1|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+1|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+1 */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 3  /* ...|d|7*m|7*m|3|   */
  ADD        /* ...|d|7*m|7*m+3|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+3|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+3 */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 4  /* ...|d|7*m|7*m|4|   */
  ADD        /* ...|d|7*m|7*m+4|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+4|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+4 */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 6  /* ...|d|7*m|7*m|6|   */
  ADD        /* ...|d|7*m|7*m+6|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+6|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+6 */
  SWAP       /* ...|7*m|d|         */
EIF          /* ...|7*m|d|         */

DUP          /* ...|7*m|d|d|       */
PUSHB_1 2    /* ...|7*m|d|d|2|     */
EQ           /* ...|7*m|d|d==2|    */
IF           /* ...|7*m|d|         */ /* if d==2 */ /* 消去: 1,5 */
  SWAP       /* ...|d|7*m|         */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 1  /* ...|d|7*m|7*m|1|   */
  ADD        /* ...|d|7*m|7*m+1|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+1|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+1 */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 5  /* ...|d|7*m|7*m|5|   */
  ADD        /* ...|d|7*m|7*m+5|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+5|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+5 */
  SWAP       /* ...|7*m|d|         */
EIF          /* ...|7*m|d|         */

DUP          /* ...|7*m|d|d|       */
PUSHB_1 3    /* ...|7*m|d|d|3|     */
EQ           /* ...|7*m|d|d==3|    */
IF           /* ...|7*m|d|         */ /* if d==3 */ /* 消去: 1,4 */
  SWAP       /* ...|d|7*m|         */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 1  /* ...|d|7*m|7*m|1|   */
  ADD        /* ...|d|7*m|7*m+1|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+1|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+1 */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 4  /* ...|d|7*m|7*m|4|   */
  ADD        /* ...|d|7*m|7*m+4|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+4|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+4 */
  SWAP       /* ...|7*m|d|         */
EIF          /* ...|7*m|d|         */

DUP          /* ...|7*m|d|d|       */
PUSHB_1 4    /* ...|7*m|d|d|4|     */
EQ           /* ...|7*m|d|d==4|    */
IF           /* ...|7*m|d|         */ /* if d==4 */ /* 消去: 0,4,6 */
  SWAP       /* ...|d|7*m|         */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 1  /* ...|d|7*m|7*m|1|   */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 4  /* ...|d|7*m|7*m|4|   */
  ADD        /* ...|d|7*m|7*m+4|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+4|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+4 */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 6  /* ...|d|7*m|7*m|6|   */
  ADD        /* ...|d|7*m|7*m+6|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+6|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+6 */
  SWAP       /* ...|7*m|d|         */
EIF          /* ...|7*m|d|         */

DUP          /* ...|7*m|d|d|       */
PUSHB_1 5    /* ...|7*m|d|d|5|     */
EQ           /* ...|7*m|d|d==5|    */
IF           /* ...|7*m|d|         */ /* if d==5 */ /* 消去: 2,4 */
  SWAP       /* ...|d|7*m|         */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 2  /* ...|d|7*m|7*m|2|   */
  ADD        /* ...|d|7*m|7*m+2|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+2|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+2 */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 4  /* ...|d|7*m|7*m|4|   */
  ADD        /* ...|d|7*m|7*m+4|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+4|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+4 */
  SWAP       /* ...|7*m|d|         */
EIF          /* ...|7*m|d|         */

DUP          /* ...|7*m|d|d|       */
PUSHB_1 6    /* ...|7*m|d|d|6|     */
EQ           /* ...|7*m|d|d==6|    */
IF           /* ...|7*m|d|         */ /* if d==6 */ /* 消去: 2 */
  SWAP       /* ...|d|7*m|         */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 2  /* ...|d|7*m|7*m|2|   */
  ADD        /* ...|d|7*m|7*m+2|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+2|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+2 */
  SWAP       /* ...|7*m|d|         */
EIF          /* ...|7*m|d|         */

DUP          /* ...|7*m|d|d|       */
PUSHB_1 7    /* ...|7*m|d|d|7|     */
EQ           /* ...|7*m|d|d==7|    */
IF           /* ...|7*m|d|         */ /* if d==7 */ /*消去: 1,3,4,6 */
  SWAP       /* ...|d|7*m|         */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 1  /* ...|d|7*m|7*m|1|   */
  ADD        /* ...|d|7*m|7*m+1|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+1|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+1 */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 3  /* ...|d|7*m|7*m|3|   */
  ADD        /* ...|d|7*m|7*m+3|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+3|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+3 */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 4  /* ...|d|7*m|7*m|4|   */
  ADD        /* ...|d|7*m|7*m+4|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+4|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+4 */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 6  /* ...|d|7*m|7*m|6|   */
  ADD        /* ...|d|7*m|7*m+6|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+6|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+6 */
  SWAP       /* ...|7*m|d|         */
EIF          /* ...|7*m|d|         */

                                      /*(if d==8)*/ /* 消去:なし */

DUP          /* ...|7*m|d|d|       */
PUSHB_1 9    /* ...|7*m|d|d|9|     */
EQ           /* ...|7*m|d|d==9|    */
IF           /* ...|7*m|d|         */ /* if d==9 */ /* 消去: 4 */
  SWAP       /* ...|d|7*m|         */
  DUP        /* ...|d|7*m|7*m|     */
  PUSHB_1 4  /* ...|d|7*m|7*m|4|   */
  ADD        /* ...|d|7*m|7*m+4|   */
  PUSHB_1 1  /* ...|d|7*m|7*m+4|1| */
  CALL       /* ...|d|7*m|         */ /* call func1 with 7*m+4 */
  SWAP       /* ...|7*m|d|         */
EIF          /* ...|7*m|d|         */

POP          /* ...|7*m|           */
POP          /* ...|               */
ENDF

数字で場合分けして、セグメントを消去する関数を呼び出してるというだけだが、全部スタックをつかって値を扱ってるので妙に長くなる。うーん、気合感。

関数3: 剰余の計算

次に、各桁の数値を計算して関数2を呼び出すような関数を作りたいところだが、先に剰余を計算する関数3を考える。

剰余の計算は、
 a \div b = c \ \mbox{余り} \ d
のとき、
 a = bc + d
が成り立つから、
 d = a -bc
を計算すればいいことがわかる。cは結果が整数になる様な割り算  c = \mathrm{int}(a/b) で求められるので、実際は
 d = a -b \cdot \mathrm{int}(a/b)
といった感じの計算になる。

さて、関数を実装するが、ここで、MULやDIVは整数ではなくF26Dot6に対して定義されている命令であり、整数a, bに対して

MUL: a * b / 64
DIV: a * 64 / b

という風な計算になることに注意する。

/* func3: calculates remainder a % b */
/* initial stack: ...|dividend a(int)|divisor b(int)|          */
/*   final stack: ...|remainder(int) = dividend a % divisor b| */
PUSHB_1
 3
FDEF     /* ...|a|b|                 */
PUSHB_1  /* ...|a|b|2|               */
 2
CINDEX   /* ...|a|b|a|               */
PUSHB_1  /* ...|a|b|2|               */
 2
CINDEX   /* ...|a|b|a|b|             */
PUSHW_1  /* ...|a|b|a|b|64.0|        */
 4096
MUL      /* ...|a|b|a|b*64|          */
DIV      /* ...|a|b|int(a/b)|        */ /* DIV: (a)*64 / (b*64) */
PUSHB_1  /* ...|a|b|int(a/b)|1.0/64| */
 1
DIV      /* ...|a|b|int(a/b)*64|     */
MUL      /* ...|a|b*int(a/b)|        */ /* MUL: (b) * (int(a/b)*64) / 64 */
SUB      /* ...|a - b*int(a/b)|      */
ENDF

関数4: 数値を表示する

さて、ここで、各桁の数値を計算して関数2を呼び出し、数値を表示する関数4を作る。

要するに、数値xについて、
x%10, x/10 % 10, x/100 % 10, x/1000 % 10
を計算して、それらの数字が各桁に表示されるように関数2を適用する。 / は整数の除算、 % は剰余を表す。

/* func4: shows the given number x */
/* initial stack: ...|x|           */
/*   final stack: ...|             */
PUSHB_1 4
FDEF          /* ...|x|                  */
DUP           /* ...|x|x|                */
PUSHB_1 10 3  /* ...|x|x|10|3|           */
CALL          /* ...|x|x%10|             */ /*call func3 with x, 10 */
PUSHB_2 0 2   /* ...|x|x%10|0|2|         */
CALL          /* ...|x|                  */ /*call func2 with x%10, 0 */
PUSHW_1 640   /* ...|x|10.0|             */
DIV           /* ...|x/10|               */
DUP           /* ...|x/10|x/10|          */
PUSHB_2 10 3  /* ...|x/10|x/10|10|3|     */
CALL          /* ...|x/10|(x/10)%10|     */ /*call func3 with x/10, 10 */
PUSHB_2 1 2   /* ...|x/10|(x/10)%10|1|2| */
CALL          /* ...|x/10|               */ /*call func2 with (x/10)%10, 1 */
PUSHW_1 640   /* ...|x/10|10.0|          */
DIV           /* ...|x/100|              */
DUP           /* ...|x/100|x/100|        */
PUSHB_2 10 3  /* ...|x/100|x/100|10|3|   */
CALL          /* ...|x/100|(x/100)%10|   */ /*call func3 with x/100, 10 */
PUSHB_2 2 2   /* ...|x/100|(x/100)%10|2|2|   */
CALL          /* ...|x/100|              */ /*call func2 with (x/100)%10, 2 */
PUSHW_1 640   /* ...|x/100|10.0|         */
DIV           /* ...|x/1000|             */
DUP           /* ...|x/1000|x/1000|      */
PUSHB_2 10 3  /* ...|x/1000|x/1000|10|3| */
CALL          /* ...|x/1000|(x/1000)%10| */ /*call func3 with x/1000, 10 */
PUSHB_2 3 2   /* ...|x/1000|(x/1000)%10|3|2| */
CALL          /* ...|x/1000|             */ /*call func2 with (x/1000)%10, 3*/
POP           /* ...|                    */
ENDF

グリフのプログラム

さて、この関数4をグリフから呼び出す。
引数の数値としては、PPEMやポイントサイズを与えてみようと思う。

PPEM

PPEMとは、Pixels/EMのことで、EMのサイズが何ピクセルで描画されるかということを示した値である。

TrueType fundamentals - Typography | Microsoft Docsによると、ppemは、
ppem = pointSize * dpi / 72
によって計算される。

PPEMを測定するのはMPPEM命令を使う。PPEMを表示するようなプログラムは次のようになる。

MPPEM     /* |PPEM|   */
PUSHB_1 4 /* |PPEM|4| */
CALL      /* |        */ /* call func4 with PPEM */
ポイントサイズ

ポイントサイズを取得するのはMPS命令を使う。

MPS       /* |PS|   */ /* Point Size */
PUSHB_1 4 /* |PS|4| */
CALL      /* |      */ /* call func4 with PS */

いろんなソフトの挙動をみる

以下で挙げた他にも、Windows 10上のFirefox, Google Chromeでも動作するのを確認した。

Windows 10 フォントビューア

PPEM:
f:id:nixeneko:20170129005803p:plain

Point Size:
f:id:nixeneko:20170129005837p:plain
冒頭でも上げたが、PPEMとPoint Sizeが別の値を指している。PPEMの計算式から、DPIが96であることがわかる(要するに、72ptの時のPPEMがDPIである)。
なお、PPEMの値はディスプレイのDPI設定を変えると変わる。
まともな実装だと思う。

Ubuntu フォントビューア

gnome-tweak-toolでヒンティングを有効にしてある。

PPEM:
f:id:nixeneko:20170129164627p:plain

Point Size:
f:id:nixeneko:20170129164707p:plain

PPEMとポイントサイズが全く同じ値を示している。dpi=72固定ということだろうか。
システム設定→ディスプレイ→「メニューとタイトルバーの拡大縮小」をいじってみたけど変化なし。

Adobe ソフト

なんとAdobe IllustratorでもTrueType命令は動く。Photoshopでも動く。以下のスクリーンショットWindows版のCS6のバージョンのものである。

Photoshop

PPEM:
f:id:nixeneko:20170129020643p:plain
謎の挙動を示す。上に挙げたppemの計算式に対し4掛けしたものが400以下かどうかで場合分けされるらしい。

if (4 * pointSize * dpi / 72 <= 400)
     ppem = 4 * pointSize * dpi / 72;
else
     ppem =     pointSize * dpi / 72;

みたいな感じか。

Point Size:
f:id:nixeneko:20170129020925p:plain
72固定っぽい。

Illustrator


Photoshopと似たような挙動を示す。

  • 基本的に最終的に画面に表示されるサイズによってppemは計算される。ズームイン・ズームアウトでも値が変わる。
  • ドラッグで拡縮してるときはppemは(pointSize * dpi / 72)になり、マウスボタン離すと上のPhotoshopと同様の場合分けされた挙動をする。
  • ポイントサイズは、Shift+ドラッグによる拡縮時(つまり、長体あるいは平体がかかってない状態)では、72未満は反映されるが、マウスボタンを離すと72固定になる。
  • アウトライン化するときは、ppemは、100%表示にしたときのppemあるいは1024のどちらか大きい方になるっぽい。ポイントサイズは72固定。
InDesign

f:id:nixeneko:20170129163439p:plain
InDesignも、Illustratorと似た挙動をするっぽい。

  • 基本的に最終的に画面に表示されるサイズによってppemは計算される。ズームイン・ズームアウトでも値が変わる。
  • ppemはPhotoshop同様に場合分けになるっぽい。
  • point sizeは72固定
After Effects

ppemは1024固定っぽい。
f:id:nixeneko:20170128105149p:plain
同様にPoint Sizeも72固定。
f:id:nixeneko:20170128112211p:plain

*1:MPPEMはprojection vectorを利用しているので。

続きを読む

CygwinからNotepad++でファイルを開く: Unixパスの扱い

Cygwinでテキストファイルを編集したいとき、普段Windowsで使っているNotepad++を使ってコマンド一発で開ければ便利だなあと思った。
丁度Ubuntuでgeditを使うようなイメージで。

他のWindowsプログラムについても、同様に対応可能であると思う。


Notepad++の実行ファイルは次にある。

C:\Program Files (x86)\Notepad++\notepad++.exe

これはCygwinUnixパスにすると

/cygdrive/c/Program Files (x86)/Notepad++/notepad++.exe

である。

案1

notepad++に実行ファイルのUnixフルパスをaliasしてみる。

alias notepad++='"/cygdrive/c/Program Files (x86)/Notepad++/notepad++.exe"'

内側をダブルクォートで囲んでいるのは、パスの中にスペースが含まれているため、そこで区切られて、ファイルが見つかりませんということになるのを避けるためである。

これだと、しかし、Unixパスで指定されたもの(“~/hoge”, “/usr/local/hoge”など)が開けない。これはNotepad++の実行ファイルにパスを通した場合でも同様だと思う。

案2

なので、引数が与えられた場合、cygpathを使ってWindowsパスに変換してから実行するような関数を作ってみた。

notepad++ () {
  if [ $# -eq 0 ]; then
    '/cygdrive/c/Program Files (x86)/Notepad++/notepad++.exe' &
  else
    for x in "$@"; do
      '/cygdrive/c/Program Files (x86)/Notepad++/notepad++.exe' "`cygpath -wa \"$x\"`" &
    done
  fi
}

引数の個数が0ならそのまま実行し、そうでなければすべての引数についてcygpathでWindowsフルパスに変換したものを引数として与えて実行している。
Notepad++のプログラムは & をつけてバックグラウンドで実行されるようにした。これをしないと複数ファイルを開けないからである。

~/.bashrc に書いておいた。


さて、とりあえず動いているのだが、これでいいのかどうか。

Undertaleをやろうとしたらゲームパッドの上が効かない

症状

ゲームパッドを接続してUndertaleを起動したところ、ゲームパッド十字キーの上が押しても反応せず、右左下には動けるのに上には動けない、あるいは上がずっと押しっぱなし状態になるかのどちらかで困っていた。
なお、別のスーファミゲームパッド(Elecom 8 Button Gamepad JC-FR08T)でも同様の状態になった。

解決策

UndertaleのJoystick Configを開き、DIR CHOICEをANALOG ONLYに変更。
f:id:nixeneko:20170121111745p:plain

POVによる入力はうまく動かないようで、Analog入力を使うように設定しないといけないっぽい。


ゲームパッド十字キー上方向の入力が効く、この当たり前のことが、あなたを決意でいっぱいにした。

フォントサイズに合わせて回転するフォントを作る(1)

TrueType命令で遊ぶシリーズ第2弾。第1弾は次を参照:


TrueType命令を利用して、フォントサイズに応じた角度だけ回転するようなフォントを作った。

完成品

f:id:nixeneko:20170118014251p:plain
コンピュータの世界広がりすぎ。

ダウンロード


アウトラインとして、M+ Fontsのmplus-1p-regular.ttfを利用した。

TrueType Instructionのコード

最終的に実装したコードは次のようになった。なおFontforgeで読み込める記述となっている(というより、Fontforgeによる逆アセンブル結果と言った方がいいか)。

'fpgm': 関数定義
PUSHB_1
 0
FDEF
PUSHB_1
 2
ADD
SVTCA[x-axis]
GC[orig]
PUSHB_1
 128
DIV
PUSHB_1
 0
SWAP
WS
PUSHB_1
 1
RCVT
PUSHB_1
 1
SWAP
WS
ENDF
PUSHB_1
 1
FDEF
DUP
ROLL
DUP
ROLL
PUSHB_1
 3
RS
MUL
SWAP
PUSHB_1
 2
RS
MUL
SUB
PUSHW_1
 4096
DIV
ROLL
ROLL
PUSHB_1
 3
RS
MUL
SWAP
PUSHB_1
 2
RS
MUL
ADD
PUSHW_1
 4096
DIV
SWAP
ENDF
PUSHB_1
 2
FDEF
DUP
DUP
SVTCA[x-axis]
GC[cur]
SWAP
SVTCA[y-axis]
GC[cur]
PUSHB_1
 1
RS
SUB
SWAP
PUSHB_1
 0
RS
SUB
MPS
PUSHB_1
 1
LOOPCALL
PUSHB_1
 0
RS
ADD
SWAP
PUSHB_1
 1
RS
ADD
SVTCA[y-axis]
PUSHB_1
 3
CINDEX
SWAP
SCFS
SVTCA[x-axis]
SCFS
POP
ENDF
PUSHB_1
 3
FDEF
DUP
PUSHB_1
 0
CALL
DUP
DUP
PUSHB_1
 0
GTEQ
IF
PUSHB_1
 2
CINDEX
PUSHB_1
 2
CINDEX
PUSHB_1
 2
CALL
PUSHB_1
 1
SUB
PUSHB_1
 20
NEG
JMPR
EIF
POP
POP
ENDF
'prep': 定数のStorage Areaへの格納
PUSHB_2
 2
 64
PUSHW_1
 4096
MUL
PUSHW_1
 512
DIV
WS
PUSHW_3
 3
 8128
 4096
MUL
PUSHW_1
 8192
DIV
WS
'cvt ': Control Value Table
index val
0 0
1 379

cvtテーブルは事前にFunit(フォントエディタで図形を定義している時の座標)で数値を記述しておき、RCVT[ ]で読みだすときにはフォントサイズ(PPEM)に応じたピクセル数となって読みだされる。書きこむ際には書きこむ単位によってWCVTF[ ] (Funit), WCVTP[ ] (Pixel unit)の2命令が使える。
0番は0にしておいたが、実際には使わなかった。
1番は回転中心のy座標である。

グリフ固有のTrueType Instruction('glyf'内)
PUSHB_2
 11
 3
CALL

11の部分にはそのグリフの最大の制御点番号が入る。全部のグリフへの適用については前回の記事を参照。


以下、どのように実装をおこなったかを説明する。

方針

グリフを回転させることを考える。回転中心はグリフの真ん中あたりとし、その座標を \left( x_c, y_c \right)とする。 \left( x_c, y_c \right)を中心に回転させるには、次のような手順をとる。

  1. 回転中心が原点に重なるよう平行移動
  2. (原点を中心に)回転
  3. 原点に移動していた回転中心が元の座標となるよう平行移動

制御点 \boldsymbol{p}_i = \left( x_i, y_i \right)に対する座標を計算する。

1. 回転中心が原点に重なるよう平行移動

制御点の座標か回転中心の座標を引く。
 \left(\begin{matrix}x_i' \\ y_i'\end{matrix}\right) = \left(\begin{matrix}x_i \\ y_i \end{matrix}\right) - \left(\begin{matrix}x_c \\ y_c\end{matrix}\right) = \left(\begin{matrix}x_i - x_c \\ y_i - y_c\end{matrix}\right)

2. (原点を中心に)回転

これは回転行列を掛けて
 \left(\begin{matrix}x_i'' \\ y_i''\end{matrix}\right) =  \left(\begin{matrix}\cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{matrix}\right) \left(\begin{matrix}x_i' \\ y_i'\end{matrix}\right) = \left(\begin{matrix}x_i'\cos\theta - y_i' \sin\theta \\ x_i' \sin\theta + y_i' \cos\theta \end{matrix}\right)
みたいな感じではあるが、 \thetaに対する \sin\theta, \cos\thetaを計算するのが面倒だったので、固定した角度 \theta_0に対応する定数 \sin\theta_0, \cos\theta_0の値を用いて、
 \left(\begin{matrix}x_i'' \\ y_i''\end{matrix}\right) =  R R R R \cdots R \left(\begin{matrix}x_i' \\ y_i'\end{matrix}\right), \ \ \ R = \left(\begin{matrix}\cos\theta_0 & -\sin\theta_0 \\ \sin\theta_0 & \cos\theta_0 \end{matrix}\right)
のように回転行列をフォントサイズに応じた回数適用することで、サイズに応じた回転を計算する。かなり効率が悪いのでどうにかしたいところではあるが……。*1

3. 原点に移動していた回転中心が元の座標となるよう平行移動

これは1.の逆である。
 \left(\begin{matrix}x_i''' \\ y_i'''\end{matrix}\right) = \left(\begin{matrix}x_i'' \\ y_i''\end{matrix}\right) + \left(\begin{matrix}x_c \\ y_c\end{matrix}\right) = \left(\begin{matrix}x_i'' + x_c \\ y_i'' + y_c\end{matrix}\right)

回転後の座標 \left( x_i''', y_i''' \right)が計算できたので、制御点 \boldsymbol{p}_iをそこに移動する。これをグリフに含まれる全ての制御点について繰り返せば、グリフの回転が計算できるという訳である。

実装

さて、より実装に近い部分について。

Storage Areaへの座標の割り付け

index 割り付け
0  x_c (回転中心x座標)
1  y_c (回転中心y座標)
2  64\sin\theta_0
3  64\cos\theta_0

Storage Areaにはこんな感じで割り付けを行い、一時的な変数として利用したり、あるいは定数を格納して使ったりしている。

回転中心の座標の取得

回転中心の取得は、y座標についてはグリフに依らず事前に値を決め打ちし、'cvt 'テーブルに1番地に座標を(Funitで)書き込んでおく。

x座標についてはphantom pointによって行う。phantom pointとは、グリフの幅や高さを取得(したり、制御)するためにラスタライザが自動で付加する制御点であり、取り扱うグリフにn個の制御点 \boldsymbol{p}_0, \cdots, \boldsymbol{p}_{n-1}がある場合、 \boldsymbol{p}_n, \boldsymbol{p}_{n+1}, \boldsymbol{p}_{n+2}, \boldsymbol{p}_{n+3}がphantom pointとなる。次に図次するように、 \boldsymbol{p}_nがx位置左端、 \boldsymbol{p}_{n+1}が右端、 \boldsymbol{p}_{n+2}が上端、 \boldsymbol{p}_{n+3}が下端を示す。

f:id:nixeneko:20170118205015p:plain

なお、 \boldsymbol{p}_{n+2}, \boldsymbol{p}_{n+3}は古いバージョン(Microsoft rasterizer v.1.7より前)では使えないので、GETINFOでバージョンを取得し、MS rasterizerのバージョンが1.7以上であることを確認してから使わないといけないそうである。
Phantom Pointsについて、詳しくは、https://www.microsoft.com/typography/otspec/ttinst.htmのInstructing TrueType Glyphsを参照。

今回はグリフ幅の中心を求めるので、 \boldsymbol{p}_{n+1}=(x_{n+1}, y_{n+1})について \frac{x_{n+1}}{2}を計算するようにする。 \boldsymbol{p}_nは原点にくるので特に値を参照しなくても幅の中心が求まる。


回転中心の座標を取得し、Storage Areaに書き込むコードを考える。

回転中心のx座標

x座標についてはphantom point  \boldsymbol{p}_{n+1}の座標、すなわちグリフの幅を取得し、それを2で割ることによって中心を得る。

グリフAに対しては次のようになる。最初にpushしている11がグリフの最大の制御点番号n-1である。

PUSHB_1
 11
PUSHB_1
 2
ADD
SVTCA[x-axis]
GC[orig]
PUSHB_1
 128
DIV
PUSHB_1
 0
SWAP
WS
GC

座標の取得は命令GCを使う。リファレンスによると、制御点番号p(整数)をpopし、その番号に対応する座標cをpushする。pushされる座標は描画するフォントサイズ(PPEM)におけるピクセル単位で表され、F26Dot6つまり小数点以下6bitの固定小数点数で表現される。

Pops p: point number (uint32)
Pushes c: coordinate location (F26Dot6)

GCには2種類あり、

  • GC[0]: 現在の点pの座標
  • GC[1]: オリジナルのアウトラインにおける点pの座標

である。これらは点を移動した場合などに得られる値が変わってくる。

x座標, y座標それぞれについて座標を取得する場合はそれぞれSVTCA[1], SVTCA[0]を事前に実行しておく。

なお、ここでF26Dot6は小数点以下6bitの固定小数点数である。これは実質、小数点数に64をかけた整数であり、F26Dot6を整数として評価した場合に64で割ると実際の数値が得られる。
例えば1.0は整数として評価すると64であり、2.0は128となる。

DIV

割り算命令DIVは、リファレンスによると

Pops n2: divisor (F26Dot6)
n1: dividend (F26Dot6)
Pushes (n1 * 64)/n2: quotient (F26Dot6)

となっていて、F26Dot6に対して計算が定義されている。
スタックが

… | n1 | n2 | 

となっているとき、n1, n2を整数として考えると(n1 * 64)/n2が計算される。

… | n1/n2(F26Dot6) | 

n1, n2はF26Dot6で与えなければいけないため、2で割るためにはn2の部分が128になる。


従って、上のコードはGC[1]で取得したx座標を2.0(128)で割っている。

WS

最終的にWS命令でStorage Areaの0番に計算した値を保存する。

Pops v: storage area value (uint32)
l: storage area location (uint32)
before … | l | v |
after  … |

Storage Areaのインデックスiに値vを書きこむ。

SWAP

なお、命令が要求するようにスタックの順番を合わせるために、SWAP命令を使っている。SWAP命令はトップ2項目の順番を入れ替える。

before: … | e1 | e2 |
after:  … | e2 | e1 |

スタックの入れ替えには他にもROLL(上から3番目の項目を先頭に)やMINDEX(指定したnについて、上からn番目の項目を先頭に)が使える。

回転中心のy座標

次に回転中心のy座標の取得をする。

PUSHB_1
 1
RCVT
PUSHB_1
 1
SWAP
WS

事前に'cvt 'テーブルの1番地に書いておいた値をRCVT命令で読みだしている。

Pops location: CVT entry number (uint32)
Pushes value: CVT value (F26Dot6)

CVTに事前に値を設定しておく場合はFunitで書くが、RCVTで読みだした値は現在のPPEMに応じたピクセル数単位に変換されて読みだされる。
上のコードでは、読みだした値をWSでStorage Areaの1番地に保存している。

関数化: 関数0

これを関数化した。
回転中心の座標の取得を行い、回転中心のx座標をStorage Areaの0番地に、y座標を1番地に書きこむ関数が関数0である。引数としてグリフの最大の制御点番号(n-1)をとる。参考までにスタックの状態とコメントを書いた*2

PUSHB_1
 0
FDEF          /* ..| n-1 |             *//* ← initial stack */
PUSHB_1       /* ..| n-1 | 2 |         */ 
 2
ADD           /* ..| n+1 |             *//* n+1: phantom point n+1 に対応 */
SVTCA[x-axis]                            /* 座標の測定をx軸方向にする */
GC[orig]      /* ..| x_(n+1) |         *//* phantom point n+1の(X)座標取得 */
PUSHB_1       /* ..| x_(n+1) | 2.0 |   *//* Uint 128 == F24Dot6 2.0 */
 128
DIV           /* ..| x_(n+1) / 2 |     */
PUSHB_1       /* ..| x_(n+1) / 2 | 0 | */
 0
SWAP          /* ..| 0 | x_(n+1) / 2 | */
WS            /* ..|                   *//* StorageArea[0] = x_(n+1) / 2 */
PUSHB_1       /* ..| 1 |               */
 1
RCVT          /* ..| CVT[1] |          *//* read control value table */
PUSHB_1       /* ..| CVT[1] | 1 |      */     /* CVT[1]は回転中心y_cに対応 */
 1
SWAP          /* ..| 1 | CVT[1] |      */
WS            /* ..|                   *//* StorageArea[1] = CVT[1] */
ENDF

グリフの回転

次に回転を計算する部分を考える。
回転行列の計算は地道に計算していくしかないのだが、定数 \cos\theta_0, \sin\theta_0について、小数点以下に12ビット使うようにすることで精度を上げようとした(つまり、6ビット分下駄をはかせている)*3

定数のセット

'prep'において、Storage Areaの2番地に \sin\theta_0 \approx \frac{1}{8}, 3番地に \cos\theta_0 \approx \frac{127}{128}の近似値を書きこんでいる。 \theta_0 \approx 0.125 \approx 7.2\mbox{°}程度の近似である。
参考:


別にこんなことしなくても、定数をPUSHWしてWSすればいいって話でもある。

PUSHB_2  /* |2|1.0|              */
 2
 64
PUSHW_1  /* |2|1.0|64.0|         */
 4096
MUL      /* |2|1.0*64.0|         */ /* 64(6ビット分)下駄をはかせる */
PUSHW_1  /* |2|1.0*64.0|8.0|     */
 512
DIV      /* |2|0.125*64.0|       */
WS       /* |                    */ /* StorageArea[2] = 0.125*64.0 */
PUSHW_3  /* |3|127.0|64.0|       */
 3
 8128
 4096
MUL      /* |3|127.0*64.0|       */ /* 64(6ビット分)下駄をはかせる */
PUSHW_1  /* |3|127.0*64.0|128.0| */
 8192
DIV      /* |3|(127/128)*64.0|   */
WS       /* |                    */ /* StorageArea[3] = (127/128)*64.0 */

'prep'はフォントサイズ(PPEM)が変更される度に呼び出されるプログラムである。

制御点の座標を取得

最初に移動させたい制御点の番号kがスタクトップに乗っているとする。

DUP           /* ..|k|k| */
DUP           /* ..|k|k|k| */
SVTCA[x-axis]                   /* X座標に設定 */
GC[cur]       /* ..|k|k|x_k| */
SWAP          /* ..|k|x_k|k| */
SVTCA[y-axis]                   /* Y座標に設定 */
GC[cur]       /* ..|k|x_k|y_k| */

STVCAで座標軸を変更しながら、GC[0]で制御点kの現在の座標の値を取得している。

平行移動

Storage Areaから回転中心のx, y座標を読みだして、座標から引いている。

PUSHB_1       /* ..|k|x_k|y_k|1|           */
 1
RS            /* ..|k|x_k|y_k|y_c|         */ /* y_c = SA[1]: 回転中心Y座標 */
SUB           /* ..|k|x_k|y_k - y_c|       */
SWAP          /* ..|k|y_k - y_c|x_k|       */
PUSHB_1       /* ..|k|y_k - y_c|x_k|0|     */
 0
RS            /* ..|k|y_k - y_c|x_k|x_c|   */ /* x_c = SA[0]: 回転中心X座標 */
SUB           /* ..|k|y_k - y_c|x_k - x_c| */
回転

回転を行う関数が関数1である。

/* Function 1: Calculates rotated coordinates with pre-defined angle T */
/* initial stack ..| y | x |             x' = x*cosT - y*sinT          */
/* final stack   ..| y'| x'|      while  y' = x*sinT + y*cosT          */
PUSHB_1
 1
FDEF     /* ..|y|x|                                 */
DUP      /* ..|y|x|x|                               */
ROLL     /* ..|x|x|y|                               */
DUP      /* ..|x|x|y|y|                             */
ROLL     /* ..|x|y|y|x|                             */
PUSHB_1  /* ..|x|y|y|x|3|                           */
 3
RS       /* ..|x|y|y|x|64*cosT|                     */ /* SA[3] = 64*cosT */
MUL      /* ..|x|y|y|64*x*cosT|                     */
SWAP     /* ..|x|y|64*x*cosT|y|                     */
PUSHB_1  /* ..|x|y|64*x*cosT|y|2|                   */
 2
RS       /* ..|x|y|64*x*cosT|y|64*sinT|             */ /* SA[2] = 64*sinT */
MUL      /* ..|x|y|64*x*cosT|64*y*sinT|             */
SUB      /* ..|x|y|64*(x*cosT - y*sinT)|            */
PUSHW_1  /* ..|x|y|64*(x*cosT - y*sinT)|64.0|       */
 4096
DIV      /* ..|x|y|x*cosT - y*sinT|                 */
ROLL     /* ..|y|x*cosT - y*sinT|x|                 */
ROLL     /* ..|x*cosT - y*sinT|x|y|                 */
PUSHB_1  /* ..|x*cosT - y*sinT|x|y|3|               */
 3
RS       /* ..|x*cosT - y*sinT|x|y|64*cosT|         */ /* SA[3] = 64*cosT */
MUL      /* ..|x*cosT - y*sinT|x|64*y*cosT|         */
SWAP     /* ..|x*cosT - y*sinT|64*y*cosT|x|         */
PUSHB_1  /* ..|x*cosT - y*sinT|64*y*cosT|x|2|       */
 2
RS       /* ..|x*cosT - y*sinT|64*y*cosT|x|64*sinT| */ /* SA[2] = 64*sinT */
MUL      /* ..|x*cosT - y*sinT|64*y*cosT|64*x*sinT| */
ADD      /* ..|x*cosT - y*sinT|64*(x*sinT + y*cosT)| */
PUSHW_1  /* ..|x*cosT - y*sinT|64*(x*sinT + y*cosT)|64.0| */
 4096
DIV      /* ..|x*cosT - y*sinT|x*sinT + y*cosT|     */
SWAP     /* ..|x*sinT + y*cosT|x*cosT - y*sinT|     */ 
ENDF                        /* ↑関数呼び出し時とx, yの順番を合わせている */

これは、スタックにy座標、x座標が積まれた状態で呼び出す。だいたい7.2°の回転を計算し、計算結果も同様にスタックに積まれる。関数が終了するときのスタック状態が関数開始時と同型になっているので、複数回関数を実行すればそれだけ回転が計算されるというわけである。

回転の繰り返し

回転回数の取得はポイント数取得命令MPSによって得られた整数値をそのまま使っている。頭悪い。
LOOPCALL命令を利用して、(MPSで得られた値)回だけ関数1を実行している。

MPS      /* ..|k|y_k-y_c|x_k-x_c|PS|   */ /* measure point size. */
                                          /* PS…ポイントサイズ, Uint */
PUSHB_1  /* ..|k|y_k-y_c|x_k-x_c|PS|1| */
 1
LOOPCALL /* ..|k|y_k-y_c|x_k-x_c|      */ /* call function 1 PS times */
             /* y_k' = y_k-y_c, x_k' = x_k-x_c are changed to y_k'', x_k'' */
平行移動
PUSHB_1       /* ..|k|y_k''|x_k''|0|       */ 
 0
RS            /* ..|k|y_k''|x_k''|x_c|     */ /* x_c = SA[0]: 回転中心X座標 */
ADD           /* ..|k|y_k''|x_k''+x_c|     */
SWAP          /* ..|k|x_k''+x_c|y_k''|     */
PUSHB_1       /* ..|k|x_k''+x_c|y_k''|1|   */
 1
RS            /* ..|k|x_k''+x_c|y_k''|y_c| */ /* y_c = SA[1]: 回転中心Y座標 */
ADD           /* ..|k|x_k''+x_c|y_k''+y_c| */

Storage Areaから回転中心のx, y座標を読みだして、計算した座標に足し算している。

制御点の移動

STVCAで座標軸を切り替えながら、SCFSを利用して、指定した制御点kを計算した座標に移動させている。

SVTCA[y-axis]                              /* Y座標に設定 */
PUSHB_1  /* ..|k|x_k''+x_c|y_k''+y_c|3| */ 
 3
CINDEX   /* ..|k|x_k''+x_c|y_k''+y_c|k| */ /*stack先頭から3番目をトップにコピー*/
SWAP     /* ..|k|x_k''+x_c|k|y_k''+y_c| */
SCFS     /* ..|k|x_k''+x_c|             */ /* 制御点kのY座標を y_k''+y_c に */
SVTCA[x-axis]                              /* X座標に設定 */
SCFS     /* ..|                         */ /* 制御点kのX座標を x_k''+x_c に */
CINDEX

ここで、CINDEXは、整数kをpopし、スタックトップからk番目の要素をコピーしてスタックに積む命令である。

Pops k: stack element number (int32)
Pushes ek: kth stack element (StkElt)

k=1のとき、DUPと同じ動作となる。

関数化: 関数2

これらの、一つの制御点kを移動する命令群を関数2としてまとめた。引数としてグリフ最大の制御点番号n-1, 編集したい制御点番号kを与えて呼び出すと制御点kを回転した位置に移動する。n-1は考えたら使ってないのでもっとシンプルにできるはず。

/* Function 2: moves a control point k to the rotated coordinates         */
/* initial stack ..| n-1 | k |    n-1: 最大の制御点番号, k: 編集する制御点番号 */
/* final stack   ..|                                                      */
PUSHB_1
 2
FDEF     /* ..|n-1|k| */
DUP      /* ..|n-1|k|k| */
DUP      /* ..|n-1|k|k|k| */
SVTCA[x-axis]                                  /* X座標に設定 */
GC[cur]  /* ..|n-1|k|k|x_k| */
SWAP     /* ..|n-1|k|x_k|k| */
SVTCA[y-axis]                                  /* Y座標に設定 */
GC[cur]  /* ..|n-1|k|x_k|y_k| */
PUSHB_1  /* ..|n-1|k|x_k|y_k|1| */
 1
RS       /* ..|n-1|k|x_k|y_k|y_c|           */ /* y_c = SA[1]: 回転中心Y座標 */
SUB      /* ..|n-1|k|x_k|y_k-y_c|           */
SWAP     /* ..|n-1|k|y_k-y_c|x_k|           */
PUSHB_1  /* ..|n-1|k|y_k-y_c|x_k|0|         */
 0
RS       /* ..|n-1|k|y_k-y_c|x_k|x_c|       */ /* x_c = SA[0]: 回転中心X座標 */
SUB      /* ..|n-1|k|y_k-y_c|x_k-x_c|       */
MPS      /* ..|n-1|k|y_k-y_c|x_k-x_c|PS|    */ /* measure point size.*/
PUSHB_1  /* ..|n-1|k|y_k-y_c|x_k-x_c|PS|1|  */
 1
LOOPCALL /* ..|n-1|k|y_k-y_c|x_k-x_c|       */ /* call function 1 PS times */
             /* y_k' = y_k-y_c, x_k' = x_k-x_c are changed to y_k'', x_k'' */
PUSHB_1  /* ..|n-1|k|y_k''|x_k''|0|         */ 
 0
RS       /* ..|n-1|k|y_k''|x_k''|x_c|       */ /* x_c = SA[0]: 回転中心X座標 */
ADD      /* ..|n-1|k|y_k''|x_k''+x_c|       */
SWAP     /* ..|n-1|k|x_k''+x_c|y_k''|       */
PUSHB_1  /* ..|n-1|k|x_k''+x_c|y_k''|1|     */
 1
RS       /* ..|n-1|k|x_k''+x_c|y_k''|y_c|   */ /* y_c = SA[1]: 回転中心Y座標 */
ADD      /* ..|n-1|k|x_k''+x_c|y_k''+y_c|   */
SVTCA[y-axis]                                 /* Y座標に設定 */
PUSHB_1  /* ..|n-1|k|x_k''+x_c|y_k''+y_c|3| */ 
 3
CINDEX   /* ..|n-1|k|x_k''+x_c|y_k''+y_c|k| * stack先頭から3番目をトップにコピー*/
SWAP     /* ..|n-1|k|x_k''+x_c|k|y_k''+y_c| */
SCFS     /* ..|n-1|k|x_k''+x_c|            *//* 制御点kのY座標を y_k''+y_c に */
SVTCA[x-axis]                                 /* X座標に設定 */
SCFS     /* ..|n-1|                        *//* 制御点kのX座標を x_k''+x_c に */
POP      /* ..|                    */ /*そういえばこのn-1は関数内で全く使ってない*/
ENDF

全ての制御点に対し適用

イデア前回の記事で使ったのと同じ。関数に渡す値を用意する部分がやや複雑になっている。
最初にスタックにn-1が積まれてることとする。

                                 /* i = n-1 とおく */
DUP       /* ..|n-1|i|         */ 
                                 /* jump_to_here */
DUP       /* ..|n-1|i|i|       */
PUSHB_1   /* ..|n-1|i|i|0|     */
 0
GTEQ      /* ..|n-1|i|i<0|     */
IF        /* ..|n-1|i|         *//* if i<0 then */
  PUSHB_1 /* ..|n-1|i|2|       */
   2
  CINDEX  /* ..|n-1|i|n-1|     */
  PUSHB_1 /* ..|n-1|i|n-1|2|   */
   2
  CINDEX  /* ..|n-1|i|n-1|i|   */
  PUSHB_1 /* ..|n-1|i|n-1|i|2| */
   2
  CALL    /* ..|n-1|i|n-1|i|   *//* call function 2 */ 
  PUSHB_1 /* ..|n-1|i|1|       */ 
   1
  SUB     /* ..|n-1|i-1|       */
  PUSHB_1 /* ..|n-1|i-1|20|    */
   20
  NEG     /* ..|n-1|i-1|-20|   */
  JMPR    /* ..|n-1|i-1|       *//* goto jump_to_here with i=i-1*/
EIF                              /* end if */

疑似コードで書くとこんな感じ。

    int i = n-1;
jump_to_here:
    if (i>=0){
      func2(n-1, i);
      i--;
      goto jump_to_here;
    }
関数化: 関数3

最終的にグリフから実行する処理を関数3としてまとめた。回転中心座標の取得と、全ての制御点に対し回転を実行することをやっている。引数としてグリフの最大の制御点番号n-1を与えて実行する。

PUSHB_1
 3
FDEF      /* ..|n-1|           */
DUP       /* ..|n-1|n-1|       */
PUSHB_1   /* ..|n-1|n-1|0|     */
 0
CALL      /* ..|n-1|           */ /* call function 0 */
DUP       /* ..|n-1|i|         */ /* i = n-1 とおく */ 
                                  /* jump_to_here */
DUP       /* ..|n-1|i|i|       */
PUSHB_1   /* ..|n-1|i|i|0|     */
 0
GTEQ      /* ..|n-1|i|i<0|     */
IF        /* ..|n-1|i|         */ /* if i<0 then */
  PUSHB_1 /* ..|n-1|i|2|       */
   2
  CINDEX  /* ..|n-1|i|n-1|     */
  PUSHB_1 /* ..|n-1|i|n-1|2|   */
   2
  CINDEX  /* ..|n-1|i|n-1|i|   */
  PUSHB_1 /* ..|n-1|i|n-1|i|2| */
   2
  CALL    /* ..|n-1|i|n-1|i|   */ /* call function 2 */ 
  PUSHB_1 /* ..|n-1|i|1|       */ 
   1
  SUB     /* ..|n-1|i-1|       */
  PUSHB_1 /* ..|n-1|i-1|20|    */
   20
  NEG     /* ..|n-1|i-1|-20|   */
  JMPR    /* ..|n-1|i-1|       */ /* goto jump_to_here */
EIF                               /* end if */
POP       /* ..|n-1|           */
POP       /* ..|               */
ENDF

よく考えてみれば関数2でn-1の値を使ってないので、ループの時点でn-1の値を保存しなくてよくて、もっとシンプルにできたはず。

各グリフのプログラム

PUSHB_2
 11
 3
CALL

11の部分にはそのグリフの最大の制御点番号が入る。
関数3を、グリフの最大の制御点番号を引数にして呼び出している。

感想

計算量がバカにならないので、割と最新のパソコンでもフォントビューワで表示するとフォントサイズごとに上からポンポンポンと順番に出てくるのがわかる。回転部分をもっと賢くさせたい。
PPEM変わるたびに呼び出されるのが'prep'なので、そこでPPEMを測定しそれに応じた回転行列のパラメータを計算しておくのがいいのだろうと思う。まあそれは追い追い。

*1:今回はグリフの制御点に対して毎回計算しているが、合成された回転行列RRRR…Rの部分はここではフォントサイズ(つまりRの個数)のみに依存するため、prepテーブルで計算しておくと随分マシになるだろうと思う。

*2:厳しいことに、Fontforgeのデバッガではコメントが使えない。空行も危ない。

*3:どこまで意味があるんだろう……。