今日は「ホウキ付ゆきだるま☃」の日!
先日の「ゆきだるま☃の日」には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だとする。)
tcfaspinしないでtcspingifする
このtcfaspinの実行手順をよく見ると、それ自体はtcfaspinとは無関係であることがわかる。すなわち、対象のLaTeX文書は別にtcfaspinを読み込んでいる必要はなく、単に「オマジナイが実行されて、“変数”が定義されている」という点だけが“普通のコンパイル”と異なるわけである。
これを利用すると、tcspingifを“流用”して好きな動き方をするアニメGIF画像をつくることができる。すなわち、LaTeX文書の内容を、「\faAllTicks
フレーム中の\faStopTicks
フレーム目」の状態を出力するようにすればよいのである。
pgfmathパッケージ2の機能を利用すると、“変数”の値を読み出すことができる3。
\pgfmathsetmacro{\vN}{\number\faStopTicks}
\pgfmathsetmacro{\vT}{\number\faStopTicks/\number\faAllTicks}
要するに要するに
以下のような感じでコードを書けばよい。
\documentclass{standalone}
\usepackage{tikz,pgfmath}
\pgfmathsetmacro{\vT}{\number\faStopTicks/\number\faAllTicks}
\pgfmathsetmacro{\vN}{\number\faStopTicks}
\begin{document}
\begin{tikzpicture}
\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]};
全体のソースは以下のようになる。
\documentclass{standalone}
\usepackage{xcolor,tikz,pgfmath}
\usepackage{scsnowman}
\usetikzlibrary{calc}
\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
というマクロで別名定義している。
\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}
\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
パラメタを追加するだけである。
\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つ分の大きさの“ビューポート”を右方向に移動させる。これで、ゆきだるま☃が左に流れているように見える。
- 開始時と終了時の状態を同じにしておくと、アニメガ反復したときに、ゆきだるま☃が滞りなく流れているように見える。
\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 (200*\vT,0) rectangle +(600,120);
\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}
増殖ゆきだるま☃
“現在時刻”ではなく“現在何フレーム目か”の値を利用した例。
\pgfmathsetmacro{\vN}{\number\faStopTicks}
- フレーム数を5とする。
- nフレーム目(n=0~4)で「n+1個のゆきだるま☃」を描く。
\documentclass{standalone}
\usepackage[svgnames]{xcolor}
\usepackage{tikz,pgfmath,scsnowman}
\newcommand{\cLet}{\pgfmathsetmacro}
\cLet{\vN}{\number\faStopTicks}
\begin{document}
\begin{tikzpicture}[x=1bp,y=1bp]
\useasboundingbox (0,0) rectangle (600,120);
\foreach \vJ in {0,...,\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関数を使った例。
\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} {
\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}
もっともっと動かしてみる
過去にツイッタァーなどで披露した「動く☃画像」のソースを出しておく。
世界平和
\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}
\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を使っている。
\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} {
\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}
ホウキ付きゆきだるま☃の日のやつ
\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}
\cLet{\vD}{2}
\cLet{\vA}{\vT*360+2}
\cLet{\zW}{\vOR*(1-cos(\vA))}
\cLet{\xW}{\vOR*(sin(\vA))}
\cLet{\vCS}{\vD/(\vD+\zW)}
\cLet{\xC}{\vCS*\xW}
\cLet{\yC}{\vCS*\yW}
\cLet{\vSS}{\vCS*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[text=black!50] at (128,68) {
\cSize{10}\fAho Broom Snowman Day};
\node at (128,33) {
\cSize{33}\fAho Tuesday};
\end{tikzpicture}
\end{document}
\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}
\cLet{\yT}{\yH+\xCS/2-\yH*\vST*\vST}
\cLet{\vEA}{exp(\vST*5)}\cLet{\vEB}{exp(\vST*(-5))}
\cLet{\vRA}{((\vEA-\vEB)/(\vEA+\vEB)*0.5+0.5)*4}
\cLet{\vRB}{mod(round(\vRA*1800)/5,360)}
\newcommand{\cTwo}{
\makebox(0,0){\tikz[scale=0.24]{\duck[
body=blue!50!green,bill=blue!50!green]}}}
\newcommand{\cZero}{
\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}
\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}