我想通过在两个 tikz 图形之间创建插值来生成一个小视频,例如在这些帧之间制作动画:
我最近发现了一个不错的animate
包,它允许生成相当高级的动画,但我不知道如何为没有直接用坐标定位的节点制作动画,而是使用例如fit
,right=of
等等name intersection
……
理想情况下,我希望有一个解决方案,我只需提供 2 张 tikz 图片,让库自动计算插值,例如基于元素的名称... 如果不存在,我会对如何自己实现它的想法感兴趣。例如,我如何才能连接到fit
/ right=of
/ name intersection
... 来计算节点的宽度和位置?(一个或多或少独立于定位策略的通用协议会很棒!)
我不想:
- 一个完全手动的解决方案,我需要手动计算节点的绝对位置。
fit
、right=of …
、name intersection=…
都很棒,我想使用它们。理想情况下,我宁愿避免定义中间节点(例如,使用幻影节点来预先计算节点的位置……)但如果别无选择,我可能会接受它。
什么是好的:
- 如果另一种语言的表现力足够好(标签,...),我不介意中间使用另一种语言,甚至采用非基于 tikz 的方法。
在我梦里:
- 这也可以改变节点形状(例如将正方形变成圆形)等等...但这肯定只是一个梦想。
\documentclass[aspectratio=169]{beamer}
% \usepackage[]{animate}
\usepackage{tikz}
\usetikzlibrary{positioning,fit}
\pgfdeclarelayer{background}
\pgfdeclarelayer{foreground}
\pgfsetlayers{background,main,foreground}
\begin{document}
\begin{frame}
I would like a smooth interpolation (just as a list of pdf pages) between these images:\\
\only<1>{
\begin{tikzpicture}
\node(abc) at (0,0) {ABC};
\node(def) at (1cm,.5cm){DEF};
\node(ghi) at (-1.5cm,.5cm){GHI};
\node[fill=green] at (0cm,-2cm){I should disappear, ideally smoothly via opacity.};
\begin{pgfonlayer}{background}
\node[fit=(abc)(ghi),fill=red,label={Hey},rounded corners] {};
\end{pgfonlayer}
\end{tikzpicture}}%
\only<2>{
\begin{tikzpicture}
\node(abc) at (0,0) {ABC};
\node(def) at (1cm,.5cm){DEF};
\node(ghi) at (-1.5cm,.5cm){GHI};
\node[fill=green,opacity=0] at (0cm,-2cm){I should disappear, ideally smoothly via opacity.};
\begin{pgfonlayer}{background}
\node[fit=(def)(abc),fill=red,label={Hey},rounded corners] {};
\end{pgfonlayer}
\end{tikzpicture}}%
\only<3>{
\begin{tikzpicture}
\node(abc) at (0,0) {ABC};
\node(def) at (1cm,.5cm){DEF};
\node(ghi) at (-1.5cm,.5cm){GHI};
\node[fill=green,opacity=0] at (0cm,-2cm){I should disappear, ideally smoothly via opacity.};
\begin{pgfonlayer}{background}
\node[right=of abc,minimum width=3cm,fill=red,label={Hey},rounded corners] {};
\end{pgfonlayer}
\end{tikzpicture}}
\end{frame}
\end{document}
% Local Variables:
% TeX-command-extra-options: "-shell-escape -halt-on-error"
% End:
--编辑--
我最终编写了我自己的自定义代码,这样我就可以输入:
\begin{frame}
\begin{createNewLoop}{nb frames=25}
\only<\blenderpointCurrentframePlusOne>{
\begin{tikzpicture}
\node(abc) at (0,0) {ABC};
\node(def) at (2cm,.5cm){DEF};
\node(ghi) at (-1.5cm,.5cm){GHI};
\myAnimatedNode[fill=green,at={(0cm,-2cm)},opacity=1-\blenderpointAnimateFraction](smoothly){I should disappear, ideally smoothly via opacity.};
\begin{pgfonlayer}{background}
\myAnimatedNode[fill=red!\blenderpointAnimatePcInvert!purple,rounded corners][fit=(abc)(ghi)][fit=(def)(abc)][label={Hey},](mynode){};
\end{pgfonlayer}
\end{tikzpicture}%
}
\end{createNewLoop}
\end{frame}
生成:
这个想法是自动绘制 2 个幻像节点 ( opacity=0
),并在这两个节点之间添加第三个节点。由于创建这两个节点很烦人,我创建了一个类似 new\node
的函数,它会自动创建 2 个幻像节点,并接受第一个节点和最后一个节点的位置作为任意样式。通过创建一个在主节点之前自动绘制节点的样式,也许可以避免这种情况……但这是第一次尝试。
优势:
- 计算边界框时会考虑幻影节点,因此图像看起来不会跳跃
- 它与节点的放置方式无关
我仍需要检查它对旋转、其他节点形状等的适应性如何……我很好奇是否存在其他解决方案。所以现在我将这个问题开放。
完整代码:
\documentclass[aspectratio=169]{beamer}
% \usepackage[]{animate}
\usepackage{tikz}
\usetikzlibrary{positioning,fit,calc,math}
\pgfdeclarelayer{background}
\pgfdeclarelayer{foreground}
\pgfsetlayers{background,main,foreground}
\begin{document}
% \myAnimatedNode[common code][code phantom 1 like fit=()][code phantom 2 like right=of ...][code in between, by default](myName){content}
\NewDocumentCommand{\myAnimatedNode}{O{}O{}O{}O{}R(){}m}{%
\message{TIKZTIKZ (XXXXXXXXXXXXXXXX \blenderpointAnimateFraction)}
% start hidden node
\node[#1,#2,opacity=0](#5-first-hidden){#6};%
% First hidden node
\node[#1,#3,opacity=0](#5-second-hidden){#6};%
% Last node to be drawn
\path let \p1=($(#5-first-hidden.west)-(#5-first-hidden.east)$),
\p2=($(#5-second-hidden.west)-(#5-second-hidden.east)$),
\n1 = {veclen(\p1)-\pgflinewidth},
\n2 = {veclen(\p2)-\pgflinewidth},
\n3 = {(1-\blenderpointAnimateFraction)*\n1 + \blenderpointAnimateFraction*\n2},
\p4=($(#5-first-hidden.south)-(#5-first-hidden.north)$),
\p5=($(#5-second-hidden.south)-(#5-second-hidden.north)$),
\n4 = {veclen(\p4)-\pgflinewidth},
\n5 = {veclen(\p5)-\pgflinewidth},
\n6 = {(1-\blenderpointAnimateFraction)*\n4 + \blenderpointAnimateFraction*\n5)} in
node[#1,at={($(#5-first-hidden.center)!\blenderpointAnimateFraction!(#5-second-hidden.center)$)}, minimum width=\n3, minimum height=\n6,#4](#5){#6};%
}
% Instead of writing a loop, we use a (more flexible) recursive function that might be practical later to,
% for instance, quit a loop before the end
\NewDocumentCommand{\createNewLoopAux}{mO{}m}{%
\pgfmathparse{int(\blenderpointCurrentframe < \blenderpointNbFrames)}%
\ifnum\pgfmathresult=1
\pgfmathparse{\blenderpointCurrentframe/(\blenderpointNbFrames-1)}%
\let\blenderpointAnimateFraction\pgfmathresult%
% It might be easier to specify stuff in % (0--100) like colors
\pgfmathparse{\blenderpointAnimateFraction*100}%
\let\blenderpointAnimatePc\pgfmathresult%
% It might be easier to invert it 100-…
\pgfmathparse{100-\blenderpointAnimatePc}%
\let\blenderpointAnimatePcInvert\pgfmathresult%
#3%
\pgfmathparse{int(\blenderpointCurrentframe + 1)}%
\let\blenderpointCurrentframe\pgfmathresult%
\pgfmathparse{int(\blenderpointCurrentframePlusOne + 1)}%
\let\blenderpointCurrentframePlusOne\pgfmathresult%
\createNewLoopAux{#1}[#2]{#3}%
\fi
}
\NewDocumentEnvironment{createNewLoop}{mO{}+b}{%
\pgfkeys{
/blenderpointAnimate/.cd,
% Make sure to include at least 2 frames
nb frames/.store in=\blenderpointNbFrames,
% Internals
current frame/.store in=\blenderpointCurrentframe,
current frame=0,
current frame plus 1/.store in=\blenderpointCurrentframePlusOne,
current frame plus 1=1,
#1
}%
\createNewLoopAux{#1}[#2]{#3}
}{}
\begin{frame}
\begin{createNewLoop}{nb frames=25}
\only<\blenderpointCurrentframePlusOne>{
\begin{tikzpicture}
\node(abc) at (0,0) {ABC};
\node(def) at (3cm,.5cm){DEF};
\node(ghi) at (-1.5cm,.5cm){GHI};
\myAnimatedNode[fill=green,at={(0cm,-2cm)},opacity=1-\blenderpointAnimateFraction](smoothly){I should disappear, ideally smoothly via opacity.};
\begin{pgfonlayer}{background}
\myAnimatedNode[fill=red!\blenderpointAnimatePcInvert!purple,rounded corners][fit=(abc)(ghi)][fit=(def)(abc)][label={Hey},](mynode){};
\end{pgfonlayer}
\end{tikzpicture}%
}
\end{createNewLoop}
\end{frame}
\end{document}
答案1
一种更紧凑且可能更强大的方法,基于动画开始和要变形的节点的最终 BBox 坐标。
\documentclass{standalone}
\usepackage{animate}
\usepackage{tikz}
\usetikzlibrary{fit,calc}
\pgfdeclarelayer{background}
\pgfsetlayers{background,main}
\def\framerate{25} % frames per second
\def\duration{2} % [s], animation duration
\begin{document}
\begin{animateinline}[autoplay,loop]{\framerate}
\multiframe{\inteval{\duration*\framerate+1}}{ % total number of frames = framerate * duration + 1
rPos=0+\fpeval{1/(\duration*\framerate)}, % intermediate partway modifier [0...1]
rOpacity=1+-\fpeval{1/(\duration*\framerate)}, % opacity [1...0]
rColorBlend=100+-\fpeval{100/(\duration*\framerate)} % colour blending [100...0]
}{
\begin{tikzpicture}
\node (abc) at (0,0) {ABC};
\node (def) at (5cm,-1cm){DEF};
\node (ghi) at (-1.5cm,.5cm){GHI};
\node [fill=green,at={(0cm,-2cm)},opacity=\rOpacity] at (0cm,-2cm) {I am fading.};
\node [rounded corners,fit=(abc)(ghi)] (start box) {}; % invisible, defines `start box'
\node [rounded corners,fit=(abc)(def)] (final box) {}; % invisible, defines `final box'
\coordinate (sw) at ($(start box.south west)!\rPos!(final box.south west)$); % "animated" BBox coordinates
\coordinate (ne) at ($(start box.north east)!\rPos!(final box.north east)$);
\begin{pgfonlayer}{background} % "morphed" node, using `fit' and animated BBox coordinates
\node [fill=red!\rColorBlend!purple,rounded corners,label={Hey},fit=(sw)(ne)] (mynode) {};
\end{pgfonlayer}
% invisible (thanks to \savebox), but extends the overall bounding box for all animation frames
\savebox0{
\node [fill, rounded corners,label={Hey},fit=(start box.south west)(start box.north east)] {}; %start position
\node [fill, rounded corners,label={Hey},fit=(final box.south west)(final box.north east)] {}; %final position
}
\end{tikzpicture}
}
\end{animateinline}
\end{document}