This question led to a new package:
pgf-blur
I have an idea for a fairly realistic-looking fading drop shadow, that works with almost arbitrary node shapes. This would also give a generic solution to at least one old question. It's the same idea I used for pst-blur that comes with PSTricks, if anyone remembers that. The idea is to get the fading by drawing the (shifted) border of the node several times with varying line width, round line joins, and different shades of gray, plus some clipping applied.
I got it to work with TikZ without transparency, but that's half the fun.
To do it with transparency, I want to draw my lines of varying shades of gray into a fading and then apply that fading to a sufficiently large black rectangle. So the fading picture depends on the path of the node. I try to do this by saving the current soft path and using Andrew Stacey's spath
library (Go to the TeX-SX Package, download the file spath.dtx
, run pdflatex spath.dtx
) to transform it.
\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}
Unfortunately, the shadow doesn't appear. I have two theories why not:
- the soft path can't be used in a different picture (namely the pgfpicture I create for the fading)
- I need to apply some transformation in
\pgfsetfading
to align the fading and the surrounding picture
Edit: concerning theory 1, removing the \pgfdeclarefading
, but keeping the {pgfpicture}
, paints the fading mask exactly where I want it, so that doesn't seem to be the issue.
So the questions is: how do I create and use a fading that depends on a path in the current picture, and use it without any further transformations applied?
Edit
As a teaser: the following are created with TikZ, but using a way too slow way to achieve the transparency.
The letters in the following were imported as path from inkscape, but the shadow is TikZ.
Edit
This is to answer a question in the comments: Why do I want to use a fading instead of just drawing the lines?
The following code was used to produce the shadows in the images above. It has a number of shortcomings as discussed below, which led to asking the original question.
\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}
The "outer" part of the shadow is rendered by repeatedly drawing and filling the shape with 10% opacity, with round line joins and decreasing line width. For the "inner" part of the shadow (where the code in the question uses clipping) the shape is drawn and filled a few more times, but now using opacity 0% for the drawing and 10% for the filling. I'm not sure what this should do according to the specs. On Acrobat Reader, the effect is to fill the shapes with 10% opacity to within 0.5*linewidth of the border, which is exactly what is needed for the shadow. On any other PDF viewer I tried, it's rendered differently.
The disadvantages of this code are:
It's slow, since it requires 7 draw/fill operations with transparency. Using a fading, only one operation with transparency is needed, and I hope this will be faster.
It's supported only by Acrobat Reader. Using a fading would at least avoid using the trick with the 0% opacity border, so I hope it would be more portable.
spath
package?spath.dtx
, runpdflatex spath.dtx
. I use it mostly to translate the soft path. An alternative may be to use a canvas transform, like TikZ' own shadow code.spath
library doesn't modify the picture bounding box. It should, and for this then I think that it needs to. Another is that fadings are centred at the origin so everything needs shifting. Taking these into account, I have something, but it's not perfect and it should be automatic.