Tikz:通过两幅图像的插值创建动画

Tikz:通过两幅图像的插值创建动画

我想通过在两个 tikz 图形之间创建插值来生成一个小视频,例如在这些帧之间制作动画:

在此处输入图片描述

我最近发现了一个不错的animate包,它允许生成相当高级的动画,但我不知道如何为没有直接用坐标定位的节点制作动画,而是使用例如fitright=of等等name intersection……

理想情况下,我希望有一个解决方案,我只需提供 2 张 tikz 图片,让库自动计算插值,例如基于元素的名称... 如果不存在,我会对如何自己实现它的想法感兴趣。例如,我如何才能连接到fit/ right=of/ name intersection... 来计算节点的宽度和位置?(一个或多或少独立于定位策略的通用协议会很棒!)

不想

  • 一个完全手动的解决方案,我需要手动计算节点的绝对位置。fitright=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}

相关内容