这个问题导致了一个新的方案的出现:
pgf-blur
我有一个想法,可以实现相当逼真的渐变阴影,适用于几乎任意的节点形状。这也将为以下问题提供一个通用解决方案:至少一个老问题。如果有人记得的话,这与我用于 PSTricks 附带的 pst-blur 的想法相同。这个想法是通过多次绘制节点的(移位)边框来实现淡入淡出,使用不同的线宽、圆形线连接和不同的灰色阴影,再加上一些剪切。
我让它与没有透明度的 TikZ 一起工作,但这也是乐趣的一半。
为了实现透明度,我想将不同深浅的灰色线条绘制成渐变效果,然后将渐变效果应用于足够大的黑色矩形。因此,渐变图片取决于节点的路径。我尝试通过保存当前软路径并使用 Andrew Stacey 的spath
库(转到TeX-SX 包,下载文件spath.dtx
,运行pdflatex spath.dtx
)对其进行转换。
\documentclass{article}
\usepackage{tikz}
\usepackage{spath}
%\usetikzlibrary{calc}
\definecolor{shadowOpacity}{gray}{0.5}
\makeatletter
\tikzset{
/tikz/render fuzz shadow/.code={
\pgfsyssoftpath@getcurrentpath{\savepath}
\pgfoonew \savespath =new spath(\savepath)
\savespath.translate path(\shiftpath,5pt,-5pt)
\pgfdeclarefading{shadowfading}{
\begin{pgfpicture}
\pgfsetroundjoin
\pgfsetlinewidth{3pt}
\pgfsetstrokecolor{black!86!shadowOpacity}
\shiftpath.use path(stroke)
\pgfsetlinewidth{2pt}
\pgfsetstrokecolor{black!72!shadowOpacity}
\shiftpath.use path(stroke)
\pgfsetlinewidth{1pt}
\pgfsetstrokecolor{black!58!shadowOpacity}
\shiftpath.use path(stroke)
\shiftpath.use path(clip)
\pgfsetfillcolor{shadowOpacity}
\shiftpath.use path(fill)
\pgfsetlinewidth{3pt}
\pgfsetstrokecolor{black!14!shadowOpacity}
\shiftpath.use path(stroke)
\pgfsetlinewidth{2pt}
\pgfsetstrokecolor{black!28!shadowOpacity}
\shiftpath.use path(stroke)
\pgfsetlinewidth{1pt}
\pgfsetstrokecolor{black!42!shadowOpacity}
\shiftpath.use path(stroke)
\end{pgfpicture}
}
\pgfsetroundjoin
\pgfsetlinewidth{3pt}
\pgfsetstrokecolor{black}
\pgfsetfillcolor{black}
\pgfsetfading{shadowfading}{}
\shiftpath.use path(stroke,fill)
},
/tikz/fuzz shadow/.style={
preaction={
render fuzz shadow,
}
}
}
\makeatother
\begin{document}
\begin{tikzpicture}
\draw[help lines,step=0.5] (0,0) grid (10,10);
\draw[thick,draw=red,fill=none,fuzz shadow] (2,3) rectangle (8,7);
\end{tikzpicture}
\end{document}
不幸的是,影子没有出现。我有两个理论来解释为什么没有出现:
- 软路径不能用于不同的图片(即我为淡入淡出创建的 pgfpicture)
- 我需要应用一些变换来
\pgfsetfading
对齐褪色和周围的图片
编辑:关于理论 1,删除\pgfdeclarefading
但保留{pgfpicture}
,会将褪色蒙版准确地绘制在我想要的位置,所以这似乎不是问题。
所以问题是:如何创建和使用依赖于当前图片中的路径的淡入淡出,并在不应用任何进一步的变换的情况下使用它?
编辑
作为一个提示:以下内容是用 TikZ 创建的,但使用了一种太慢的方式来实现透明度。
以下字母是从 inkscape 作为路径导入的,但阴影是 TikZ。
编辑
这是为了回答评论中的一个问题:为什么我要使用淡入淡出而不是仅仅画线条?
以下代码用于生成上图中的阴影。它存在许多缺点,如下所述,这导致了最初的问题。
\documentclass{article}
\usepackage{tikz}
\usepackage{spath}
\usetikzlibrary{shadows}
\tikzset{
/tikz/slow fuzz shadow/.style={
shadow scale=1,
shadow xshift=.5ex,
shadow yshift=-.5ex,
general shadow={%
opacity=0.1,
draw=black!75,fill=black!75,
line join=round,
line width=3pt,
#1,
},
general shadow={%
opacity=0.1,
draw=black!75,fill=black!75,
line join=round,
line width=2pt,
#1,
},
general shadow={%
opacity=0.1,
draw=black!75,fill=black!75,
line join=round,
line width=1pt,
#1,
},
general shadow={%
opacity=0.1,
fill=black!75,
#1,
},
general shadow={%
draw opacity=0,
fill opacity=0.1,
line width=1pt,
draw=black!75,fill=black!75,
#1,
},
general shadow={%
draw opacity=0,
fill opacity=0.1,
line width=2pt,
draw=black!75,fill=black!75,
#1,
},
general shadow={%
draw opacity=0,
fill opacity=0.1,
line width=3pt,
draw=black!75,fill=black!75,
#1,
}
}
}
\begin{document}
\begin{tikzpicture}
\draw[help lines,step=0.5] (0,0) grid (10,10);
\draw[thick,draw=black,fill=white,slow fuzz shadow] (2,3) rectangle (8,7);
\end{tikzpicture}
\end{document}
阴影的“外部”部分是通过反复绘制和填充具有 10% 不透明度的形状来呈现的,线条连接圆润,线条宽度减小。对于阴影的“内部”部分(问题中的代码使用裁剪),形状被绘制和填充了几次,但现在使用 0% 的不透明度进行绘制,使用 10% 的不透明度进行填充。我不确定这是什么意思应该根据规格执行。在 Acrobat Reader 上,效果是将形状填充为 10% 不透明度,范围在边框的 0.5*linewidth 以内,这正是阴影所需要的。在我尝试过的任何其他 PDF 查看器上,它的呈现方式都不同。
该代码的缺点是:
它很慢,因为它需要 7 次绘制/填充透明度操作。使用淡入淡出,只需要一次透明度操作,而且我希望这样会更快。
只有 Acrobat Reader 支持此功能。使用淡入淡出至少可以避免使用 0% 不透明度边框的技巧,因此我希望它的便携性会更强。
答案1
这导致spath
库中增加了一些功能,包括更好的边界框处理和更广泛的路径转换。最新版本在发射台(它仍然应该被视为字母代码)。
解决原始问题:问题是由边界框引起的。原始spath
代码没有提到边界框,因此构建的淡入淡出大小为零。此外,PGF 将淡入淡出视为“黑框”,不知道内部坐标系,因此它不知道如何关联内部和外部坐标。因此,它只是将淡入淡出置于原点的中心,并让用户从那里进行分类(有一种方法可以将其适合当前路径,但这里也不想要)。因此,还需要一种方法让库spath
自动计算出将淡入淡出置于正确位置所需的转换。由于淡入淡出位于中心,这意味着要知道路径(边界框)的中点。
添加起来并不困难spath
,并且是迫切需要的,因此要求添加它并不是什么困难。
当我开始尝试使用代码来产生所需的效果时,我发现自己遇到了一个又一个的问题 - 总是一些小问题,从来没有什么大问题。(这就是为什么我在评论中问为什么使用淡入淡出:理论上直接绘制路径似乎更容易。然而,经过实验后,我决定淡入淡出实际上是正确的方法。)
接下来的一些内容可能依赖于渲染程序。
首先,我想说的是,问题中概述的机制非常巧妙。要理解为什么我需要对其进行一些修改,我觉得我需要解释一下它的作用(或者说,它打算做什么)。
这个想法是采取给定的路径并创建一个衰退褪色可以被认为是阿尔法蒙版它给出了每个像素的不透明度。这个想法是,这种淡入淡出应该是路径的填充版本(以“半透明”填充),并在边缘平滑地变为完全透明。进行这种平滑的变化有点麻烦,因为它需要根据像素与路径的距离指定像素的颜色(这与蒙版中产生的不透明度有关)。这个问题使用了一种巧妙的方法,即以不同的宽度绘制路径,每个路径具有不同的不透明度。然后,将这种淡入淡出用于原始路径的填充黑色副本以创建阴影。由于边界附近的不透明度不同,效果是产生阴影。
然而,问题中的代码有一个微妙的缺陷。最终的路径是抚摸和填充。这两个都应用了淡入淡出效果,但淡入淡出效果是依次应用的 - 而不是同时应用的。我尝试使用透明度组来解决这个问题,但它只使用了整体不透明度,而不是淡入淡出效果。
以下是屏幕截图:
粗灰线是由于绘图部分接收双重复制而产生的:一次来自填充,一次来自笔触的内部部分。
因此,我们只能填充最终路径。这意味着,我们要么必须扩大路径,要么必须调整原始路径的不透明度向边界淡出的方式,以便它们在到达路径时淡出,而不是在到达描边的边缘时淡出(请记住,当描边路径时,墨水会流向两侧,因此实际路径位于绘制像素的中间)。扩大路径似乎是一个好主意,因为阴影通常比原始对象大,但通过更改指定不透明度的方式,可以更准确地获得精确的淡出效果。(为了获得更大的阴影,我们在开始时就扩大了路径。)这样做的实际结果是,我们首先填充 alpha 蒙版,然后用越来越薄的透明描边描边路径。
接下来,我们来看看如何使拐角变得模糊。同样,问题中使用的方法非常巧妙:在绘制渐变图片中的路径时使用圆形连接。这种方法的缺点是,连接仅在弯曲的外侧是圆形的。内侧是方形的,我们现在看到的是内侧。为了解决这个问题,我添加了调用 PGF 的方法使路径段之间的连接变圆的功能,这涉及修改路径本身,而不仅仅是渲染方式。
有了这些,我们现在就可以得到一个合理的阴影效果,而不需要大量笨拙的代码(至少,感觉不像也尴尬的)。
这是代码(显然,需要最新的spath
):
\documentclass{article}
\usepackage{tikz}
\usepackage{spath}
\definecolor{shadowOpacity}{gray}{0.5}
\begin{document}
\tikzset{
/tikz/render fuzz shadow/.code={
\pgfoonew \savespath =new spath()
\savespath.use current path()
\savespath.mid point()
\pgfgetlastxy{\midx}{\midy}
\pgfmathsetmacro\midx{-.1*\midx}
\pgfmathsetmacro\midy{-.1*\midy}
\savespath.transform path(,{1.1}{0.0}{0.0}{1.1}{\midx pt}{\midy pt})
\savespath.round corners(,14pt)
\savespath.clone(\fadepath)
\fadepath.prepare()
\pgfdeclarefading{shadowfading}{
\pgfpicture
\pgfsetfillcolor{shadowOpacity}
\fadepath.use path(fill)
\foreach \wth/\op in {%
14/0,
12/14,
10/28,
8/42,
6/58,
4/72,
2/86%
} {
\pgfsetlinewidth{\wth pt}
\pgfsetstrokecolor{black!\op!shadowOpacity}
\fadepath.use path(stroke)
}
\endpgfpicture
}
\pgfsetfillcolor{black}
\savespath.translate path(,14pt,-14pt)
\pgfsetfading{shadowfading}{\pgftransformshift{\savespath.mid point()}}
\pgfsetstrokecolor{black}
\pgfsetlinewidth{14pt}
\savespath.use path(fill)
},
/tikz/fuzz shadow/.style={
preaction={
render fuzz shadow,
}
}
}
\begin{tikzpicture}
\draw[help lines,step=0.5] (0,0) grid (10,10);
\draw[thick,draw=red,fill=white,fuzz shadow] (2,3) rectangle (8,7);
\end{tikzpicture}
\end{document}
请注意我甚至不再需要了\makeatletter
!
此文使用的新内容:
use current path
:按照其名称进行操作:采用 TikZ/PGF 内存中的当前路径并将其安装为path
给定对象的路径spath
。mid point
:计算路径边界框的中间。transform path
x -> Ax + b
:对路径中的每个坐标应用仿射变换(仿射意味着它采用2x2 矩阵 A 和向量 b的形式)round corners
:应用 PGF 方法将所有角都弄圆
还有一些要么是内部使用的,要么是我想过要用但最终没有用的东西。
(有点编辑:自从我写上面的内容已经过了一段时间,但我实际上还没有发布任何内容。)
我想以一张更壮观的图片作为结尾。在这样做的过程中,我发现了一些错误spath
,并使用一些 pgfkeys 使上面的内容更具可定制性。显然,边界框中还有一些错误需要解决。
这里还有一些代码 - 这些代码无法在其他任何人的机器上运行,因为和stix-letters
不是stix-italic-letters
合适的包:它们是通过 fontforge 和 perl 脚本运行 STIX 字体以将它们转换为 TikZ 可以理解的 SVG 路径的结果。
\documentclass{standalone}
\usepackage{tikz}
\usepackage{spath}
\usepackage{stix-letters}
\usepackage{stix-italic-letters}
\usetikzlibrary{svg.path}
\definecolor{shadowOpacity}{gray}{0.5}
\begin{document}
\tikzset{
/tikz/fuzz shadow blur/.initial=1,
/tikz/fuzz shadow corners/.initial=14pt,
/tikz/fuzz shadow scale/.initial=1,
/tikz/fuzz shadow offset/.initial={\pgfpoint{14pt}{-10pt}},
/tikz/render fuzz shadow/.code={
\pgfoonew \fadepath =new spath()
\fadepath.use current path()
\fadepath.mid point()
\pgfgetlastxy{\midx}{\midy}
\pgfmathsetmacro\midx{(1 - \pgfkeysvalueof{/tikz/fuzz shadow scale})*\midx}
\pgfmathsetmacro\midy{(1 - \pgfkeysvalueof{/tikz/fuzz shadow scale})*\midy}
\fadepath.transform path(,{\pgfkeysvalueof{/tikz/fuzz shadow scale}}{0.0}{0.0}{\pgfkeysvalueof{/tikz/fuzz shadow scale}}{\midx pt}{\midy pt})
\fadepath.round corners(,\pgfkeysvalueof{/tikz/fuzz shadow corners})
\fadepath.prepare()
\pgfdeclarefading{shadowfading}{
\pgfpicture
\foreach \wth/\op in {%
14/86,
12/72,
10/58,
8/42,
6/28,
4/14,
2/0%
} {
\pgfmathsetmacro\wth{\wth * \pgfkeysvalueof{/tikz/fuzz shadow blur}}
\pgfsetlinewidth{\wth pt}
\pgfsetstrokecolor{black!\op!shadowOpacity}
\fadepath.use path(stroke)
}
\pgfsetfillcolor{shadowOpacity}
\fadepath.use path(fill)
\endpgfpicture
}
\pgfsetfillcolor{black}
\fadepath.min bb()
\pgfgetlastxy{\sx}{\sy}
\fadepath.max bb()
\pgfgetlastxy{\ex}{\ey}
\pgfkeysvalueof{/tikz/fuzz shadow offset}
\pgfgetlastxy{\ox}{\oy}
\fadepath.mid point()
\pgfgetlastxy{\midx}{\midy}
\pgfmathsetmacro\sx{\sx + \ox - 14pt}
\pgfmathsetmacro\sy{\sy + \oy - 14pt}
\pgfmathsetmacro\ex{\ex + \ox + 14pt - \sx}
\pgfmathsetmacro\ey{\ey + \oy + 14pt - \sy}
\pgfmathsetmacro\midx{\midx + \ox}
\pgfmathsetmacro\midy{\midy + \oy}
\pgfsetfading{shadowfading}{\pgftransformshift{\pgfpoint{\midx pt}{\midy pt}}}
\pgfpathrectangle{\pgfpoint{\sx pt}{\sy pt}}{\pgfpoint{\ex pt}{\ey pt}}
\pgfusepath{fill}
},
/tikz/fuzz shadow/.style={
preaction={
render fuzz shadow,
}
}
}
\PrepareLetter{T}
\PrepareLetter{i}
\PrepareLetter[italic]{k}
\PrepareLetter{z}
\begin{tikzpicture}
\draw[help lines,step=0.5] (0,0) grid (10,10);
\draw[thick,draw=red,fill=white,fuzz shadow] (1,3) rectangle (9,7);
\begin{scope}[
fuzz shadow offset={\pgfpoint{5pt}{-4pt}},
fuzz shadow corners=0pt,
fuzz shadow blur=.5,
]
\stixnormalT.translate path(,2cm,4cm)
\stixnormalT.use path with tikz(fill,fuzz shadow)
\stixnormalT.max bb()
\pgfgetlastxy{\sx}{\sy}
\stixnormali.translate path(,\sx,4cm)
\stixnormali.use path with tikz(fill,fuzz shadow)
\stixnormali.max bb()
\pgfgetlastxy{\sx}{\sy}
\stixitalick.translate path(,\sx,4cm)
\stixitalick.use path with tikz(fill,fuzz shadow)
\stixitalick.max bb()
\pgfgetlastxy{\sx}{\sy}
\stixnormalz.translate path(,\sx,4cm)
\stixnormalz.use path with tikz(fill,fuzz shadow)
\end{scope}
\path (0,0); % bounding box hack
\end{tikzpicture}
\end{document}
生成的结果如下: