tikz 边界框/裁剪:曲线空间太大

tikz 边界框/裁剪:曲线空间太大

我对 TikZ 的自动裁剪/自动计算边界框存在问题tikzpicture

请看以下示例:

\documentclass{article}
\usepackage{tikz}
\begin{document}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam scelerisque massa quis nibh egestas, sed aliquam justo gravida. Integer eget felis vel erat auctor sagittis. In eget ligula eu velit rutrum sodales sed at velit. Proin id blandit ante, tristique bibendum magna.

\begin{center}
\begin{tikzpicture}
\node[draw,circle] (A) at (0,0){A};
\node[draw,circle] (B) at (3,3){B};
\draw (A) to (B);
\end{tikzpicture}
\end{center}

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam scelerisque massa quis nibh egestas, sed aliquam justo gravida. Integer eget felis vel erat auctor sagittis. In eget ligula eu velit rutrum sodales sed at velit. Proin id blandit ante, tristique bibendum magna.

\begin{center}
\begin{tikzpicture}
\node[draw,circle] (A) at (0,0){A};
\node[draw,circle] (B) at (3,3){B};
\draw[bend left=90,looseness=2] (A) to (B);
\draw[bend right=90,looseness=2] (A) to (B);
\end{tikzpicture}
\end{center}

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam scelerisque massa quis nibh egestas, sed aliquam justo gravida. Integer eget felis vel erat auctor sagittis. In eget ligula eu velit rutrum sodales sed at velit. Proin id blandit ante, tristique bibendum magna.
\end{document}

这将产生以下输出: 在此处输入图片描述

如您所见,直线图片的裁剪效果非常好。但是,曲线图片前后的空白太多(不必要的)。

boundingbox我知道我可以通过更改或简单地使用来手动解决这个问题\vspace,但是有没有一种自动的方法来获得准确的边界框?

(注:这与这个问题但那里的答案似乎对自动的计算,主要首先检查边界框是什么,然后应用某种剪辑。)

答案1

这是我尝试获得一种自动化方法。阅读这一页知道如何分割贝塞尔曲线。

limit bb我用两个参数定义新风格:

  1. 实际边界框和完美边界框之间的最大距离。
  2. 应用于路径的操作(绘制、填充...)。

这种新风格会自动且递归地分割所有贝塞尔曲线,以删除太远的控制点。

在此处输入图片描述

\documentclass[tikz]{standalone}
\usetikzlibrary{calc,decorations.pathreplacing}
\tikzset{
  bezier/controls/.code args={(#1) and (#2)}{
    \def\mystartcontrol{#1}
    \def\mytargetcontrol{#2}
  },
  bezier/limit/.store in=\mylimit,
  bezier/limit=1cm,
  bezier/.code={
    \tikzset{bezier/.cd,#1}
    \tikzset{
      to path={
        let
        \p0=(\tikztostart),    \p1=(\mystartcontrol),
        \p2=(\mytargetcontrol), \p3=(\tikztotarget),
        \n0={veclen(\x1-\x0,\y1-\y0)},
        \n1={veclen(\x3-\x2,\y3-\y2)},
        \n2={\mylimit}
        in  \pgfextra{
          \pgfmathtruncatemacro\ok{max((\n0>\n2),(\n1>\n2))}
        }
        \ifnum\ok=1 %
        let
        \p{01}=($(\p0)!.5!(\p1)$), \p{12}=($(\p1)!.5!(\p2)$), \p{23}=($(\p2)!.5!(\p3)$),
        \p{0112}=($(\p{01})!.5!(\p{12})$), \p{1223}=($(\p{12})!.5!(\p{23})$),
        \p{01121223}=($(\p{0112})!.5!(\p{1223})$)
        in
        to[bezier={controls={(\p{01}) and (\p{0112})}}]
        (\p{01121223})
        to[bezier={controls={(\p{1223}) and (\p{23})}}]
        (\p3)
        \else
        [overlay=false] .. controls (\p1) and (\p2) ..  (\p3) [overlay=true]
        \fi
      },
    }%, <-- Comma here results in "Missing character: There is no , in font nullfont!"
  },
  limit bb/.style n args={2}{
    overlay,
    decorate,
    decoration={
      show path construction,
      moveto code={},
      lineto code={\path[#2] (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast);},
      curveto code={
        \path[#2]
        (\tikzinputsegmentfirst)
        to[bezier={limit=#1,controls={(\tikzinputsegmentsupporta) and (\tikzinputsegmentsupportb)}}]
        (\tikzinputsegmentlast);
      },
      closepath code={\path[#2] (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast);},
    },
  },
  limit bb/.default={1mm}{draw},
}

\begin{document}
\begin{tikzpicture}
  \node[draw,circle] (A) at (0,0){A};
  \node[draw,circle] (B) at (3,3){B};
  \draw[limit bb={1mm}{draw=red},bend left=90,looseness=2] (A) to (B);
  \draw[limit bb={1mm}{draw=blue},bend right=90,looseness=2] (A) to (B);
  \draw[green] (current bounding box.south west) rectangle (current bounding box.north east);
\end{tikzpicture}
\end{document}

答案2

默认情况下,边界框的一部分来自

\def\pgf@lt@moveto#1#2{%
  \pgf@protocolsizes{#1}{#2}%
  \pgfsyssoftpath@moveto{\the#1}{\the#2}%
}
\def\pgf@lt@lineto#1#2{%
  \pgf@protocolsizes{#1}{#2}%
  \pgfsyssoftpath@lineto{\the#1}{\the#2}%
}
\def\pgf@lt@curveto#1#2#3#4#5#6{%
  \pgf@protocolsizes{#1}{#2}%
  \pgf@protocolsizes{#3}{#4}%
  \pgf@protocolsizes{#5}{#6}%
  \pgfsyssoftpath@curveto{\the#1}{\the#2}{\the#3}{\the#4}{\the#5}{\the#6}%
}

这就是我们看到的原因所有控制点均参与:控制点直接传递给边界框计算(\pgf@protocolsizes)。要解决这个问题,只能在里面进行数学运算\pgf@lt@curveto。如果算上 TeX 以外的编程语言,这个主题肯定是重复的。例如一种查找封闭贝塞尔曲线边界框的算法?在堆栈溢出中。

但在 TeX 中做数学运算很困难。但仍然可以牺牲一些效率来获得一个相当可接受的结果。例如:因为 3(1-t)t^2,3(1-t)^2t≤4/9,我们知道

xmax≤max(xA,xD)+4/9|xB-max(xA,xD)|+4/9|xC-max(xA,xD)|

所以右边可以改进计算。

答案3

这是一个非常迟来的强力解决方案。警告: 这是一个非常缓慢的解决。:适用于全部路径。这个想法只是沿着路径移动并记录坐标。(这比我最初想象的要棘手一些,因为 TiZ 沿路径使用共动框架。除了此处的情况外,这在所有情况下都很好。在此示例中,此“问题”通过发出 得到“解决” \pgftransformreset。)此答案带有样式get path extrema,它确定极值点,贝塞尔曲线就是一个例子,否则很好的答案失败。以下是问题中描述的场景的一个应用。

\documentclass[tikz,border=3.14pt]{standalone}
\usetikzlibrary{calc,decorations,decorations.markings}
\tikzset{get path extrema/.style={decorate,decoration={markings, 
    mark=between positions 0 and 1 step 0.3pt with
      {\xdef\mypos{\pgfkeysvalueof{/pgf/decoration/mark info/distance from
      start}}
      \begin{pgfinterruptpath}
      \ifdim\mypos=0pt
      \coordinate (start) at (0,0);
      \fi
      \coordinate (here) at (0,0);
      \pgftransformreset
      \path let \p1 = ($(here) - (start)$) in \pgfextra{\xdef\myx{\x1}
      \xdef\myy{\y1}}; 
      \ifdim\mypos=0pt
      \xdef\myxmin{\myx}
      \xdef\myymin{\myy}
      \xdef\myxmax{\myx}
      \xdef\myymax{\myx}
      \xdef\mypostop{(\myx,\myy)}
      \xdef\myposbottom{(\myx,\myy)}
      \xdef\myposleft{(\myx,\myy)}
      \xdef\myposright{(\myx,\myy)}
      \fi
      \ifdim\myx<\myxmin
      \xdef\myposleft{(\myx,\myy)}
      \xdef\myxmin{\myx}
      \fi
      \ifdim\myx>\myxmax
      \xdef\myposright{(\myx,\myy)}
      \xdef\myxmax{\myx}
      \fi
      \ifdim\myy<\myymin      
      \xdef\myposbottom{(\myx,\myy)}
      \xdef\myymin{\myy}
      \fi
      \ifdim\myy>\myymax
      \xdef\mypostop{(\myx,\myy)}
      \xdef\myymax{\myy}
      \fi
      \end{pgfinterruptpath}
      }},
      path picture={
      \path[shift=(start)] \myposleft coordinate (#1-left) --
    \mypostop coordinate (#1-top) --
    \myposright coordinate (#1-right) --
    \myposbottom coordinate (#1-bottom) -- cycle;
    }}}
\begin{document}
\begin{tikzpicture}
\node[draw,circle] (A) at (0,0){A};
\node[draw,circle] (B) at (3,3){B};
\begin{pgfinterruptboundingbox}
\draw[bend left=90,looseness=2,postaction={get path extrema=test1}] (A) to (B);
\draw[bend right=90,looseness=2,postaction={get path extrema=test2}] (A) to (B);
\end{pgfinterruptboundingbox}
\path[red] (test1-left)  -- (test1-top)
-- (test1-right)-- (test1-bottom) -- cycle;
\draw[red] (test2-left)  -- (test2-top)
-- (test2-right)-- (test2-bottom) -- cycle;
\end{tikzpicture}
\end{document}

在此处输入图片描述

红色轮廓只是一个例子。我知道这是一个非常晚的答案,我也认为一定有办法让事情变得更快。本质上,人们需要告诉 TipgfinterruptboundingboxZ在绘制路径构造中弹出的辅助路径时发出一些命令。但这样做远远超出了我的能力范围。

相关内容