今日は「ホウキ付ゆきだるま☃」の日!
先日の「ゆきだるま☃の日」にはLaTeXで「回転ゆきだるま☃画像」を作成するためのtcspingifというスクリプトを紹介した。tcspingifを使って作れる「回転☃」は「自転☃」であるが、少し(あるいは、かなり)頑張れば、上図のような「公転☃」、あるいはもっと別の動きを伴った「動く☃」のGIF画像をつくることもできる。その方法について解説する。
tcspingifはなぜ動くのか
tcfaspinパッケージはanimateパッケージの機能を使って「動くPDF文書」を作っているが、「動くPDF文書」は画像に変換できない。では、tcspingifはなぜGIF画像を作れるのかというと、実はtcfaspinの中に秘密がある。
tcfaspinを読み込む前に次のようなオマジナイを実行しておく。
\chardef\faStopTicks=18 \chardef\faAllTicks=32
TeX言語のコードなので理解する必要は全くないのだが、フインキとしては「\faStopTicks
と\faAllTicks
という2つの“変数”を定義して、それぞれ18と32を“代入”する」のようなことを行っている1。このオマジナイが実行済である場合、tcfaspinパッケージはanimateを読み込まなくなり、また、\faSpin
命令の動作が変わり、単に「32フレーム中の18フレーム目」の状態を出力するようになる。この場合に出力されるPDF文書はanimateを使っていないため普通にImageMagickで画像に変換できる。
この「tcfaspinの裏仕様」を利用して、tcfaspinは次のような手順で「動くGIF画像」を作っている。(フレーム数が32だとする。)
- n=0~31の各々について:
- 上記のオマジナイ(ただし
\faStopTicks
をnとする)を実行した上で対象のLaTeXソースをコンパイルして、普通のPDF文書を作る。 - ImageMagickでPDF文書をPNG画像に変換する。これが「nフレーム目のPNG画像」となる。
- 上記のオマジナイ(ただし
- 再びImageMagickを用いて、「0~31フレーム目のPNG画像」をアニメGIF画像に変換する。
tcfaspinしないでtcspingifする
このtcfaspinの実行手順をよく見ると、それ自体はtcfaspinとは無関係であることがわかる。すなわち、対象のLaTeX文書は別にtcfaspinを読み込んでいる必要はなく、単に「オマジナイが実行されて、“変数”が定義されている」という点だけが“普通のコンパイル”と異なるわけである。
これを利用すると、tcspingifを“流用”して好きな動き方をするアニメGIF画像をつくることができる。すなわち、LaTeX文書の内容を、「\faAllTicks
フレーム中の\faStopTicks
フレーム目」の状態を出力するようにすればよいのである。
pgfmathパッケージ2の機能を利用すると、“変数”の値を読み出すことができる3。
% "変数" \faStopTicks の値を \vN に代入する \pgfmathsetmacro{\vN}{\number\faStopTicks} % \faStopTicks ÷ \faAllTicks の値を \vT に代入する \pgfmathsetmacro{\vT}{\number\faStopTicks/\number\faAllTicks}
要するに要するに
以下のような感じでコードを書けばよい。
\documentclass{standalone} \usepackage{tikz,pgfmath} % \vT は"アニメの開始を0、終了を1とした場合の現在時刻"の値 \pgfmathsetmacro{\vT}{\number\faStopTicks/\number\faAllTicks} % \vN は"現在何フレーム目か"の値 \pgfmathsetmacro{\vN}{\number\faStopTicks} % (その他諸々の設定...) \begin{document} \begin{tikzpicture} % \vT や \vN の値を利用して"現在のフレーム"を描画する \end{tikzpicture} \end{document}
※もちろん、このようにtcspingifを“流用”したLaTeX文書は「tcspingif専用」であり、普通にコンパイルして「動くPDF文書」をつくることはできないことに注意。
※tcfaspinを読み込まない場合、tcfaspinの設定からフレーム数を取得することができないため、tcspingifで--ticks
(-t
)オプションの指定が必須になる。
ゆきだるま☃を動かしてみる
※以下では、前回と同じく「DPI値を72にした(tcspingifで-d 72
を指定)上でTikZでx=1bp,y=1bp
を指定して座標の単位をピクセルと一致させる」設定を利用する。
手始めに、ゆきだるま☃を単純に横に動かしてみる。具体的に「600×120ピクセルの画像で、ゆきだるま☃を(60,60)から(540,60)に動かす」ことにする。先ほど示した雛形の\vT
(現在時刻)を使うことにすると、ゆきだるま☃を置く点は「始点・終点間の\vT
倍内分点」となる。calcライブラリの内分点表記を利用すると、tikzpicture内は以下のように書ける。
\node at ($(60,60)!\vT!(540,60)$) { \scsnowman[scale=15,hat,snow,arms,buttons,muffler=red]};
全体のソースは以下のようになる。
% ↓以下のコマンドで変換 % tcspingif -e pdflatex -d 72 -t 32 -b 1 sample01.tex \documentclass{standalone} \usepackage{xcolor,tikz,pgfmath} \usepackage{scsnowman}% ゆきだるま☃! \usetikzlibrary{calc}% 内分点表記を使う % \vT は"現在時刻"(0~1の範囲) \pgfmathsetmacro{\vT}{\number\faStopTicks/\number\faAllTicks} \begin{document} \begin{tikzpicture}[x=1bp,y=1bp] \useasboundingbox (0,0) rectangle (600,120); \node at ($(60,60)!\vT!(540,60)$) { \scsnowman[scale=15,hat,snow,arms,buttons,muffler=red]}; \end{tikzpicture} \end{document}
このLaTeXソースを以下のコマンドで画像に変換する。
tcspingif -e pdflatex -d 72 -t 32 -b 1 sample01.tex
※以下、ソースリストの先頭のコメントに変換用のコマンドラインを載せる。
実際に画像を表示して結果を確認してみよう。
これで「自分で動きを定義する」ことができた。あとは“\vT
と描画の間の処理”を適宜増やしていくことで、好きな動きを表現するだけである。
まずは「ゆきだるま☃が始点と終点の間を往復する」ようにしてみよう。pgfmathの式の三項演算子を利用して、「\vT
が0→1を動くと、\vA
が0→1→0と動く」ような新たな変数\vA
を定義する。
\cLet{\vA}{(\vT<0.5) ? 2*\vT : 2-2*\vT}
あとは、\node
の座標において、\vT
の代わりに\vA
を使う。
\node at ($(60,60)!\vA!(540,60)$) {
全体のソースは以下の通り。ここで、\pgfmathsetmacro
という命令名は長いので、\cLet
というマクロで別名定義している。
% tcspingif -e pdflatex -d 72 -t 32 -b 1 sample02.tex \documentclass{standalone} \usepackage{xcolor,tikz,pgfmath,scsnowman} \newcommand{\cLet}{\pgfmathsetmacro} \usetikzlibrary{calc} \cLet{\vT}{\number\faStopTicks/\number\faAllTicks} \cLet{\vA}{(\vT<0.5) ? 2*\vT : 2-2*\vT} \begin{document} \begin{tikzpicture}[x=1bp,y=1bp] \useasboundingbox (0,0) rectangle (600,120); \node at ($(60,60)!\vA!(540,60)$) { \scsnowman[scale=15,hat,snow,arms,buttons,muffler=red]}; \end{tikzpicture} \end{document}
今度は「ゆきだるま☃の動きをもう少し自然にする」ために、水平位置の変数\vA
を\vT
の二次関数に変えてみる。
\cLet{\vB}{(\vT<0.5) ? 2*\vT : 2-2*\vT} \cLet{\vA}{(\vA<0.5) ? 2*\vB*\vB : -2*\vB*\vB+4*\vB-1}
% tcspingif -e pdflatex -d 72 -t 32 -b 1 sample03.tex \documentclass{standalone} \usepackage{xcolor,tikz,pgfmath,scsnowman} \newcommand{\cLet}{\pgfmathsetmacro} \usetikzlibrary{calc} \cLet{\vT}{\number\faStopTicks/\number\faAllTicks} \cLet{\vB}{(\vT<0.5) ? 2*\vT : 2-2*\vT} \cLet{\vA}{(\vA<0.5) ? 2*\vB*\vB : -2*\vB*\vB+4*\vB-1} \begin{document} \begin{tikzpicture}[x=1bp,y=1bp] \useasboundingbox (0,0) rectangle (600,120); \node at ($(60,60)!\vA!(540,60)$) { \scsnowman[scale=15,hat,snow,arms,buttons,muffler=red]}; \end{tikzpicture} \end{document}
さらに、ゆきだるま☃に回転を加えてみよう。これは\node
命令にrotate
パラメタを追加するだけである。
% tcspingif -e pdflatex -d 72 -t 32 -b 1 sample04.tex \documentclass{standalone} \usepackage{xcolor,tikz,pgfmath,scsnowman} \newcommand{\cLet}{\pgfmathsetmacro} \usetikzlibrary{calc} \cLet{\vT}{\number\faStopTicks/\number\faAllTicks} \cLet{\vB}{(\vT<0.5) ? 2*\vT : 2-2*\vT} \cLet{\vA}{(\vA<0.5) ? 2*\vB*\vB : -2*\vB*\vB+4*\vB-1} \begin{document} \begin{tikzpicture}[x=1bp,y=1bp] \useasboundingbox (0,0) rectangle (600,120); \node[rotate=-360*\vA] at ($(60,60)!\vA!(540,60)$) { \scsnowman[scale=15,hat,snow,arms,buttons,muffler=red]}; \end{tikzpicture} \end{document}
もっと動かしてみる
某ZR氏のツイッタァーでよくみるやつ
「無限に並んだゆきだるま☃が流れている」画像。
- ゆきだるま☃を8つ横に並べる。
- その上で、ゆきだるま☃6つ分の大きさの“ビューポート”を右方向に移動させる。これで、ゆきだるま☃が左に流れているように見える。
- 開始時と終了時の状態を同じにしておくと、アニメガ反復したときに、ゆきだるま☃が滞りなく流れているように見える。
% tcspingif -e pdflatex -d 72 -t 32 -b 1.5 sample05.tex % ↑少し速度を上げた \documentclass{standalone} \usepackage{xcolor,tikz,pgfmath,scsnowman} \newcommand{\cLet}{\pgfmathsetmacro} \cLet{\vT}{\number\faStopTicks/\number\faAllTicks} \begin{document} \begin{tikzpicture}[x=1bp,y=1bp] % ビューポート左下座標を (0,0)→(200,0) と動かす. % "+(...)"は相対指定. \useasboundingbox (200*\vT,0) rectangle +(600,120); % 赤マフラー☃と青マフラー☃を交互に4回描く \foreach \vJ in {0,...,3} { \node at (50+200*\vJ,60) { \scsnowman[scale=14,hat,snow,arms,buttons,muffler=red]}; \node at (150+200*\vJ,60) { \scsnowman[scale=14,hat,arms,buttons,muffler=blue, sweat,mouthshape=tight]}; } \end{tikzpicture} \end{document}
増殖ゆきだるま☃
“現在時刻”ではなく“現在何フレーム目か”の値を利用した例。
% \vN は"現在何フレーム目か"の値 \pgfmathsetmacro{\vN}{\number\faStopTicks}
- フレーム数を5とする。
- nフレーム目(n=0~4)で「n+1個のゆきだるま☃」を描く。
% tcspingif -e pdflatex -d 72 -t 5 -b 1 sample06.tex % ↑フレーム数(-t)は5で固定 \documentclass{standalone} \usepackage[svgnames]{xcolor} \usepackage{tikz,pgfmath,scsnowman} \newcommand{\cLet}{\pgfmathsetmacro} % \vN は"現在何フレーム目か"の値 \cLet{\vN}{\number\faStopTicks} \begin{document} \begin{tikzpicture}[x=1bp,y=1bp] \useasboundingbox (0,0) rectangle (600,120); \foreach \vJ in {0,...,\vN} {% 上限が \vN \node at (60+120*\vJ,60) { \scsnowman[scale=15,muffler=Red,hat=Green, buttons=RoyalBlue,arms=Brown,snow=SkyBlue]}; } \end{tikzpicture} \end{document}
ゆきだるま☃ウェーブ
pgfmathでは初等関数が利用できるので、sin関数を使った例。
% tcspingif -e pdflatex -d 72 -t 32 -b 1.5 sample07.tex \documentclass{standalone} \usepackage{xcolor,tikz,pgfmath,scsnowman} \newcommand{\cLet}{\pgfmathsetmacro} \cLet{\vT}{\number\faStopTicks/\number\faAllTicks} \begin{document} \begin{tikzpicture}[x=1bp,y=1bp] \useasboundingbox (0,0) rectangle (600,120); \foreach \vJ in {0,...,9} { % xcolorの混合率の値, 100→0 と動く. \cLet{\vC}{100*(1-\vJ/9)} % 垂直変位. 水平位置と時刻で決まる. \cLet{\yD}{30*sin(360*(\vJ/9+\vT))} \node at (30+60*\vJ,60+\yD) { \scsnowman[scale=9,arms,buttons, muffler=red!\vC!blue,hat=red!\vC!blue!75!black]}; } \end{tikzpicture} \end{document}
もっともっと動かしてみる
過去にツイッタァーなどで披露した「動く☃画像」のソースを出しておく。
世界平和
% tcspingif -e pdflatex -d 200 -t 32 -b 0.5 sample08.tex % ↓200dpiで480×480ピクセルになるように調整 \documentclass[margin=1.36bp]{standalone} \usepackage{xcolor,tikz,pgfmath} \usepackage{scsnowman,tikzducks} \definecolor{mc0}{rgb}{0.8,0,0} \definecolor{mc1}{rgb}{0.5,0.3,0} \definecolor{mc2}{rgb}{0.2,0.6,0} \definecolor{mc3}{rgb}{0,0.7,0.1} \definecolor{mc4}{rgb}{0,0.4,0.4} \definecolor{mc5}{rgb}{0,0.1,0.7} \definecolor{mc6}{rgb}{0.2,0,0.6} \definecolor{mc7}{rgb}{0.5,0,0.3} \scsnowmandefault{hat,arms,buttons,snow,scale=6} \newcommand{\cLet}{\pgfmathsetmacro} \cLet{\vT}{\number\faStopTicks/\number\faAllTicks} \cLet{\vD}{\vT*360} \pagecolor{black!0.5} \begin{document}%↓座標単位は既定のまま(1cm) \begin{tikzpicture} \useasboundingbox (-3,-3) rectangle (3,3); \node at (- 0+\vD:2) {\scsnowman[muffler=mc0]}; \node at (- 45+\vD:2) {\scsnowman[muffler=mc1]}; \node at (- 90+\vD:2) {\scsnowman[muffler=mc2]}; \node at (-135+\vD:2) {\scsnowman[muffler=mc3]}; \node at (-180+\vD:2) {\scsnowman[muffler=mc4]}; \node at (-225+\vD:2) {\scsnowman[muffler=mc5]}; \node at (-270+\vD:2) {\scsnowman[muffler=mc6]}; \node at (-315+\vD:2) {\scsnowman[muffler=mc7]}; \node at (0,0) {\tikz[scale=0.4]{\duck}}; \end{tikzpicture} \end{document}
フルパワー清涼感
これは普通にtcfaspinを使っている。
% tcspingif -e pdflatex -d 72 -t 32 -b 3 sample09.tex \documentclass{standalone} \usepackage{ifthen,xcolor,tikz,pgfmath,scsnowman} \newcommand{\cLet}{\pgfmathsetmacro} \pagecolor{blue!30!green!6}%←背景色 \colorlet{coolhat}{blue!80!green!75!black} \colorlet{coolmuffler}{blue!50!green} \colorlet{coolarms}{green!30!black!50} \colorlet{coolsnow}{blue!80!green!40} \usepackage{tcfaspin}%←回します☃ \newcommand\cESnowman[1]{% \cPutSnowman{muffler=coolmuffler,hat=coolhat,#1}} \newcommand\cOSnowman[1]{% \cPutSnowman{muffler=coolhat,hat=coolmuffler,#1}} \newcommand\cPutSnowman[1]{% \node at (\vX*100+50,\vY*100+50) {% \faSpin{\scsnowman[% scale=10,arms=coolarms,snow=coolsnow,buttons=coolsnow, #1]}};} \begin{document} \begin{tikzpicture}[x=1bp,y=1bp] \useasboundingbox (0,0) rectangle (600,600); \foreach \vX in {0,1,...,5} \foreach \vY in {0,1,...,5} { % pgfmathの条件演算子だけだと実行の分岐ができないので % ifthenを併用する. ただし, \ifthenelse の条件文には % 式が書けず不便なので, "pgfmathで真偽値を求めて変数に % 入れて \ifthenelse でそれを見る"ことにする. \cLet{\vBroom}{\vX==5 && \vY==2} \cLet{\vNote}{\vX==4 && \vY==4} \cLet{\vOdd}{isodd(\vX+\vY)} \ifthenelse{\vBroom>0}{% ホウキ付き \cOSnowman{broom=coolarms!75!black} }{\ifthenelse{\vNote>0}{% ♪付き \cESnowman{note=coolsnow!90} }{\ifthenelse{\vOdd>0}{ \cOSnowman{} }{\cESnowman{}}}} } \end{tikzpicture} \end{document}
ホウキ付きゆきだるま☃の日のやつ
% tcspingif -e pdflatex -d 72 -t 64 -b 0.5 sample10.tex \documentclass{standalone} \usepackage{xcolor,tikz,pgfmath,scsnowman} \usepackage{type1cm} %↓アホなフォント \newcommand*{\fAho}{\usefont{OT1}{cmfr}{m}{it}{}} \newcommand*{\cSize}[1]{\fontsize{#1}{0}\selectfont} \definecolor{mydbrown}{rgb}{0.3,0.0,0.0} \definecolor{mylbrown}{rgb}{0.7,0.5,0.0} \newcommand{\cLet}{\pgfmathsetmacro}% \cLet{\vT}{\number\faStopTicks/\number\faAllTicks} \cLet{\vOR}{6}% ☃の軌道の半径 \cLet{\yW}{-2}% ☃のy座標 \cLet{\vD}{2}% キャンバスの位置(z=−2にある) \cLet{\vA}{\vT*360+2}% 軌道上の角度 %↑開始時に中央に見えるように開始地点を補正している \cLet{\zW}{\vOR*(1-cos(\vA))}% ☃のz座標 \cLet{\xW}{\vOR*(sin(\vA))}% ☃のx座標 \cLet{\vCS}{\vD/(\vD+\zW)}% (一点透視による倍率) \cLet{\xC}{\vCS*\xW}% ☃のキャンバス上x座標 \cLet{\yC}{\vCS*\yW}% ☃のキャンバス上y座標 \cLet{\vSS}{\vCS*24}% ☃のキャンバス上の大きさ %↑実際の大きさ(scale)が24 \newcommand{\cSnowman}[1]{% \scsnowman[scale=#1, arms=mydbrown,muffler=red,broom=mylbrown]} \begin{document} \begin{tikzpicture}[x=1bp,y=1bp] \useasboundingbox (0,0) rectangle (256,320); \node at (\xC*50+128,\yC*50+286) {\cSnowman{\vSS}}; %↓例の日めくりカレンダーのアレ \node at (128,260) {% \cSize{29}\fAho August}; % \node at (128,168) {% % \cSize{190}\fAho 18}; \node[text=black!50] at (128,68) {% \cSize{10}\fAho Broom Snowman Day}; \node at (128,33) {% \cSize{33}\fAho Tuesday}; \end{tikzpicture} \end{document}
Happy Snowman’s Day☃
% tcspingif.pl -e pdflatex -t 64 -d 108 snowmansday2020.tex \documentclass{standalone} \usepackage[T1]{fontenc} \usepackage{lmodern,pifont} \usepackage{xcolor,scsnowman,tikzducks,pgfmath} \newcommand*{\cLet}{\pgfmathsetmacro} \setlength{\unitlength}{1bp} %↓背景色 \colorlet{bgcolor}{blue!30!green!10} \pagecolor{bgcolor} % \cLet{\xCS}{80}\cLet{\yCS}{320}% キャンバスサイズ \cLet{\vT}{\number\faStopTicks/\number\faAllTicks} \cLet{\vST}{(\vT-0.5)*2} \cLet{\yH}{\yCS-\xCS} \cLet{\xT}{\xCS/2}% ☃のx座標 %↑ただし実際にはずらして(2つ)置いている \cLet{\yT}{\yH+\xCS/2-\yH*\vST*\vST}% ☃のy座標 %↑時刻の二次関数 \cLet{\vEA}{exp(\vST*5)}\cLet{\vEB}{exp(\vST*(-5))} \cLet{\vRA}{((\vEA-\vEB)/(\vEA+\vEB)*0.5+0.5)*4}% 回転 %↑時刻をアフィン変換してtanhしたもの. 4回転している. \cLet{\vRB}{mod(round(\vRA*1800)/5,360)}% 回転(度数) %↑0.2度の整数倍に丸めた % \newcommand{\cTwo}{% アヒル的"2" \makebox(0,0){\tikz[scale=0.24]{\duck[% body=blue!50!green,bill=blue!50!green]}}} \newcommand{\cZero}{% 雪結晶的"0" \fontsize{16}{0}\selectfont %↓重ね書きはイマイチ \makebox(0,1){\color{blue!50!green}\ding{108}}% \makebox(1,0){\color{bgcolor}\scalebox{0.8}{\ding{93}}}} % \begin{document}%↓なんとpicture環境 \begin{picture}(400,360)(0,-20) \put(120,\yT){\makebox(0,0){\rotatebox[origin=c]{-\vRB}{% \scsnowman[hat=blue!80!green!75!black, muffler=blue!50!green, arms=green!30!black!50, snow=blue!80!green!40, scale=12]}}} \put(280,\yT){\makebox(0,0){\rotatebox[origin=c]{\vRB}{% \scsnowman[hat=blue!50!green, muffler=blue!80!green!75!black, arms=green!30!black!50, snow=blue!80!green!40, scale=12]}}} \put(200,260){\makebox(0,0){\textcolor{blue!50!green!60}{% \fontsize{48}{0}\sffamily\bfseries\ding{100}}}} \put(200,200){\makebox(0,0){\textcolor{blue!85!black}{% \fontsize{28}{40}\sffamily\bfseries\itshape {Happy Snowman's Day}}}} \put(200,144){\cTwo} \put(200,112){\cZero} \put(200,080){\cTwo} \put(200,048){\cZero} \end{picture} \end{document}
-
【TeX言語者向け解説】整数定数として用いるために、制御綴
\faStopTicks
を値18をもつchardefトークンと定義している。\faAllTicks
も同様。↩ -
TikZを読み込むとpgfmathが内部で読み込まれるので、この機能を利用するのが手っ取り早いだろう。(そして、scsnowmanパッケージはTikZを内部で読み込む。)↩
-
【TeX言語者向け解説】
\faStopTicks
はchardefトークンであるため\number\faStopTicks
を1回展開するとその整数値の十進数字列に展開される。pgfmathの式の中で「数字列に展開されるトークン列は数値として扱われるため、この方法で値が読み出せるわけである。ちなみに、chardefトークンそのものはpgfmathの式の中で数値として扱われない。↩