[Tex/LaTex] define a new shape with predefined filling and shading to be used in decoration

decorationstikz-pgf

I defined a new shape that draws a flower with predefined filling and coloring:

\makeatletter
\pgfdeclareshape{flower}{
  \savedanchor{\center}{%
    \pgfpointorigin}
  \anchor{center}{\center}
\backgroundpath{%  
\begingroup
  \tikz@mode
    \iftikz@mode@draw
      \foreach \a in {60,120,...,360} {
        \pgftransformrotate{\a}
        \pgfpathellipse{\pgfpointxy{0}{0.7}}{\pgfpointxy{0.25}{0}}{\pgfpointxy{0}{0.5}}
      }
      \pgfpathcircle{\pgfpoint{0}{0}}{0.4cm}

      \let\my@color\tikz@strokecolor!60!\tikz@fillcolor
      \foreach \a in {30,60,...,360} {%
        \pgftransformrotate{\a+7}%
        \pgfpathmoveto{\pgfpointxy{0}{0.1}}
        \def\y{0.6+0.05*rand}
        \pgfpathlineto{\pgfpointxy{0}{\y}}
        \pgfpathcircle{\pgfpointxy{0}{\y}}{0.05cm}
      }
    \fi
    \iftikz@mode@fill
      \begin{scope}
        \foreach \a in {60,120,...,360} {
          \pgftransformrotate{\a}
          \pgfpathellipse{\pgfpointxy{0}{0.7}}{\pgfpointxy{0.25}{0}}{\pgfpointxy{0}{0.5}}
        }
        \pgfusepath{clip}
        \pgfpathcircle{\pgfpointxy{0}{0}}{1.2cm}
        \pgfutil@colorlet{tikz@radial@outer}{\tikz@fillcolor!70}
        \pgfutil@colorlet{tikz@radial@inner}{\tikz@fillcolor!60!\tikz@strokecolor}
        \pgfshadepath{radial}{0}
        \pgfusepath{}
      \end{scope}
      % \tikz@mode@clipfalse

      \let\my@strokecolor\tikz@strokecolor
      \let\my@fillcolor\tikz@fillcolor

      \pgfsetfillcolor{\my@strokecolor}
      \pgfpathcircle{\pgfpointxy{0}{0}}{0.4cm}
      \pgfusepath{fill}          

      \foreach \a in {30,60,...,360} {%
        \pgftransformrotate{\a+7}%
        \pgfpathmoveto{\pgfpointxy{0}{0.1}}
        \def\y{0.5+0.05*rand}
        \pgfpathlineto{\pgfpointxy{0}{\y}}
        \pgfpathcircle{\pgfpointxy{0}{\y}}{0.05cm}
      }
      \let\my@color\my@fillcolor
      \pgfutil@colorlet{\my@color}{\my@strokecolor!60!\my@fillcolor}
      \pgfsetstrokecolor{\my@color}
      \pgfsetfillcolor{\my@color}
      \pgfusepath{stroke,fill}
    \fi
   \endgroup
 }
}
\makeatother

which results in a nicely filled and colored flower when used as:

\node[flower, fill=pink, draw=purple] {};

Flower

Still, I have several problems and questions, for which I am not able to find an answer myself.

1) if I do not use \begin{scope} and \end{scope}, I do not know how to stop clipping which I do in the filling section. Is there a way to avoid using scopes? Or it is completely fine to have them there?

2) In the drawing of stamens, their height can be slightly changed using random function

    \def\y{0.6+0.05*rand}
    \pgfpathlineto{\pgfpointxy{0}{\y}}
    \pgfpathcircle{\pgfpointxy{0}{\y}}{0.05cm}

but each time the value of \y is recalculated and I do not know how to fix it here.

3) If I do not specify the colors of stroke and fill

fill=pink, draw=purple

I get compiling errors. I even get errors if I use

\node[flower, fill, color=red] {};

In that case, how can I use the default colors?

4) I would like to define a new decoration that would decorate a path with flowers of random size, moreover I expect these flowers to be exactly the same as on the picture above. Here is the code of my decoration:

\pgfdeclaredecoration{flowers}{initial}{
  \state{initial}[width=1cm]
{
\pgfmathparse{round(rnd*90)}
\pgftransformrotate{\pgfmathresult}
\pgfmathparse{0.2+rand*0.1}
\pgftransformscale{\pgfmathresult}
\pgfsetlinewidth{\pgfmathresult*\pgflinewidth}
\pgfsetcolor{pink}
\pgfsetfillcolor{pink}
\pgfsetstrokecolor{purple}
\pgfnode{flower}{center}{}{}{\pgfusepath{stroke,fill}}
}
\state{final}
{
  \pgfpathmoveto{\pgfpointdecoratedpathlast}
}
}

My first problem is that I get "plain" flowers by calling

\draw[decorate, decoration={flowers}] (-4,-3) -- (4,-3);

Line decorated with flowers

instead of a nice filling/coloring. What should I modify in the definition of flower?

Then, I would prefer to use random step between the flowers, how it can be done?

Best Answer

This is probably still a little way off because there are a few places where I'm not sure what you're after.

  1. if I do not use \begin{scope} and \end{scope}, I do not know how to stop clipping which I do in the filling section. Is there a way to avoid using scopes? Or it is completely fine to have them there?

    You have to limit the clip somehow and scopes are the right way to do this. You can use the lower-level \pgfscope and \endpgfscope to be consistent with your use of the \pgf... commands.

  2. In the drawing of stamens, their height can be slightly changed using random function

    \def\y{0.6+0.05*rand}
    \pgfpathlineto{\pgfpointxy{0}{\y}}
    \pgfpathcircle{\pgfpointxy{0}{\y}}{0.05cm}
    

    but each time the value of \y is recalculated and I do not know how to fix it here.

    I see several uses of this code and I'm guessing that you want them all to be mutually consistent. There are two problems: one with the code above and one with the global reuse. With the code above then \y gets computed twice and each time it uses a different random number. So instead of \def\y{...} you want to make \y the result of that computation. For this, use \pgfmathsetmacro\y{...}. The second problem is addressed by resetting the random seed for \pgfmath at appropriate places. This is done using \pgfmathsetseed.

  3. If I do not specify the colors of stroke and fill

    fill=pink, draw=purple

    I get compiling errors. I even get errors if I use

    \node[flower, fill, color=red] {};

    In that case, how can I use the default colors?

    TikZ only sets \tikz@strokecolor and \tikz@fillcolor if the draw or fill options were given explicit colours. A general colour is saved as tikz@color (note that this is a colour name not a macro) so you need to use that. I recommend putting a "get the right colours" bit at the top of your code and then using your own names. As I said in my comment, I'm not completely clear as to which colours correspond to which actions (fill, stroke, fade) on the parts of the flower so my code (below) will probably get this wrong.

  4. I would like to define a new decoration that would decorate a path with flowers of random size, moreover I expect these flowers to be exactly the same as on the picture above. Here is the code of my decoration:

    My first problem is that I get "plain" flowers by calling instead of a nice filling/coloring. What should I modify in the definition of flower?

    Then, I would prefer to use random step between the flowers, how it can be done?

    The plain flowers comes from the fact that you are defining the colours in the decoration using low-level commands (\pgfsetstrokecolor) but extracting the colours from the TikZ level commands (\tikz@strokecolor). There isn't, as far as I can see, a way to extract the current stroke colour so we have to set them using the TikZ method. The same goes for the stroke and fill actions. As you're using \tikz@mode@... in your code you have to use the TikZ method of specifying draw and fill.

    The width is evaluated afresh each time so you can set it to a new dimen and then adjust this dimension on each run.

Here's my adaptation of your code implementing all the above. As I've said, I don't think I get quite what you're looking for in a few places but hopefully it is close enough that you can see where to go next.

\documentclass{article}
%\url{http://tex.stackexchange.com/q/110047/86}
\usepackage{tikz}
\usetikzlibrary{decorations.pathreplacing}

\makeatletter

\def\pgfmathgetseed#1{%
  \let#1=\pgfmath@rnd@z}

\pgfdeclareshape{flower}{
  \savedanchor{\center}{%
    \pgfpointorigin}
  \anchor{center}{\center}
\backgroundpath{%  
\begingroup
    \ifx\tikz@strokecolor\pgfutil@empty
    \def\my@strokecolor{tikz@color}
    \else
    \let\my@strokecolor\tikz@strokecolor
    \fi
    \ifx\tikz@fillcolor\pgfutil@empty
    \def\my@fillcolor{tikz@color}
    \else
    \let\my@fillcolor\tikz@fillcolor
    \fi
    \pgfmathgetseed{\flower@seed}
  \tikz@mode
    \iftikz@mode@draw
      \foreach \a in {60,120,...,360} {
        \pgftransformrotate{\a}
        \pgfpathellipse{\pgfpointxy{0}{0.7}}{\pgfpointxy{0.25}{0}}{\pgfpointxy{0}{0.5}}
      }
      \pgfpathcircle{\pgfpoint{0}{0}}{0.4cm}
    \pgfmathsetseed{\flower@seed}
      \foreach \a in {30,60,...,360} {%
        \pgftransformrotate{\a+7}%
        \pgfpathmoveto{\pgfpointxy{0}{0.1}}
        \pgfmathsetmacro\y{0.6+0.05*rand}
        \pgfpathlineto{\pgfpointxy{0}{\y}}
        \pgfpathcircle{\pgfpointxy{0}{\y}}{0.05cm}
      }
    \pgfsetstrokecolor{\my@strokecolor}
      \pgfusepath{stroke}
    \fi
    \iftikz@mode@fill
    \pgfscope
        \foreach \a in {60,120,...,360} {
          \pgftransformrotate{\a}
          \pgfpathellipse{\pgfpointxy{0}{0.7}}{\pgfpointxy{0.25}{0}}{\pgfpointxy{0}{0.5}}
        }
        \pgfusepath{clip}
        \pgfpathcircle{\pgfpointxy{0}{0}}{1.2cm}
        \pgfutil@colorlet{tikz@radial@outer}{\my@fillcolor!70}
        \pgfutil@colorlet{tikz@radial@inner}{\my@fillcolor!60!\my@strokecolor}
        \pgfshadepath{radial}{0}
        \pgfusepath{discard}
      \endpgfscope
      % \tikz@mode@clipfalse

      \pgfsetfillcolor{\my@strokecolor}
      \pgfpathcircle{\pgfpointxy{0}{0}}{0.4cm}
      \pgfusepath{fill}          

    \pgfmathsetseed{\flower@seed}
      \foreach \a in {30,60,...,360} {%
        \pgftransformrotate{\a+7}%
        \pgfpathmoveto{\pgfpointxy{0}{0.1}}
        \pgfmathsetmacro\y{0.6+0.05*rand}
        \pgfpathlineto{\pgfpointxy{0}{\y}}
        \pgfpathcircle{\pgfpointxy{0}{\y}}{0.05cm}
      }
      \let\my@color\my@fillcolor
      \pgfutil@colorlet{\my@color}{\my@strokecolor!60!\my@fillcolor}
      \pgfsetstrokecolor{\my@color}
      \pgfsetfillcolor{\my@color}
      \pgfusepath{stroke,fill}
    \fi
   \endgroup
 }
}

\newdimen\flower@sep
\flower@sep=1cm\relax

\pgfdeclaredecoration{flowers}{initial}{
  \state{initial}[width=\flower@sep]
{
\pgfmathparse{round(rnd*90)}
\pgftransformrotate{\pgfmathresult}
\pgfmathparse{0.2+rand*0.1}
\pgftransformscale{\pgfmathresult}
\pgfsetlinewidth{\pgfmathresult*\pgflinewidth}
\def\tikz@strokecolor{purple}
\def\tikz@fillcolor{pink}
\tikz@mode@drawtrue
\tikz@mode@filltrue
\pgfnode{flower}{center}{}{}{}
\pgfmathsetlength\flower@sep{1cm + rand*0.5cm}
\global\flower@sep=\flower@sep
}
\state{final}
{
  \pgfpathmoveto{\pgfpointdecoratedpathlast}
}
}


\makeatother

\begin{document}

\tikz \node[flower, fill=pink, draw=purple] {};

\tikz \node[flower, fill, color=red] {};


\tikz \draw[decorate, decoration={flowers}] (-4,-3) -- (4,-3);

\end{document}

Flowers


Update I've gone through the code a little and removed some of the repetition and tidied up a few things. Comments in code.

\documentclass{article}
%\url{http://tex.stackexchange.com/q/110047/86}
\usepackage{tikz}
\usetikzlibrary{decorations.pathreplacing}

\makeatletter

\tikzset{
  flower shade colour/.store in=\my@shadecolour,
  flower shade color/.store in=\my@shadecolour,
  flower shade colour=white
}

\pgfdeclareshape{flower}{
  \savedanchor{\center}{%
    \pgfpointorigin}
  \anchor{center}{\center}
%
% We use \beforebackgroundpath since we're handling the stroke and
% fills ourselves.
%
\beforebackgroundpath{%
\begingroup
%
% Do our very best to set the colours.
%
% Was a stroke colour set explicitly (via draw=<colour>)?
    \ifx\tikz@strokecolor\pgfutil@empty
% No, so test if a general colour was set.
    \@ifundefinedcolor{tikz@color}{%
% No, so take the current colour.
    \def\my@strokecolor{.}%
      }{%
% General colour was set so use that
    \def\my@strokecolor{tikz@color}%
      }%
    \else
% Stroke colour was set, so use that
    \let\my@strokecolor\tikz@strokecolor
    \fi
%
% Now the same for the fill colour.
% Was a fill colour set explicitly?
    \ifx\tikz@fillcolor\pgfutil@empty
% No, so was a general colour set?
    \@ifundefinedcolor{tikz@color}{%
% No, so reuse the stroke colour.
    \let\my@fillcolor\my@strokecolor
      }{%
% General colour was set, use that.
    \def\my@fillcolor{tikz@color}%
      }%
    \else
% Specific fill colour was set, so use that.
    \let\my@fillcolor\tikz@fillcolor
    \fi

%
% Test for the mode
%
    \tikz@mode
%
% Draw the petals.
%
    \foreach \a in {60,120,...,360} {
          \pgftransformrotate{\a}
      \pgfpathellipse{\pgfpointxy{0}{0.7}}{\pgfpointxy{0.25}{0}}{\pgfpointxy{0}{0.5}}
        }
%
% If fill is requested, shade them using the fill colour and the
% (extra) shade colour.
%
    \iftikz@mode@fill
        \pgfutil@colorlet{tikz@radial@outer}{\my@fillcolor}
        \pgfutil@colorlet{tikz@radial@inner}{\my@shadecolour}
        \pgfshadepath{radial}{0}
        \pgfusepath{discard}
    \else
    \iftikz@mode@draw
%
% If fill mode was not requested but draw mode was, stroke the path.
%
    \pgfusepath{stroke}
    \fi
    \fi

%
% Now the centre of the flower; filled if requested, if not filled
% then drawn if requested.
%
    \pgfpathcircle{\pgfpointxy{0}{0}}{0.4cm}
    \iftikz@mode@fill
      \pgfsetfillcolor{\my@strokecolor}
      \pgfusepath{fill}          
    \else
    \iftikz@mode@draw
      \pgfsetstrokecolor{\my@strokecolor}
    \pgfusepath{stroke}
    \fi
    \fi

%
% Lastly the stamen.
% If fill was requested then we also stroke the paths (to get the
% stalk part - biology was never my strong point).
% Note that we push the circles out by their radius so that if
% drawn then it still looks right.
%
      \foreach \a in {30,60,...,360} {%
        \pgftransformrotate{\a+7}%
        \pgfpathmoveto{\pgfpointxy{0}{0.1}}
        \pgfmathsetmacro\y{0.6+0.05*rand}
        \pgfpathlineto{\pgfpointxy{0}{\y}}
        \pgfpathcircle{\pgfpointxy{0}{\y + 0.05}}{0.05cm}
      }
      \let\my@color\my@fillcolor
      \pgfutil@colorlet{\my@color}{\my@strokecolor!60!\my@fillcolor}
      \pgfsetstrokecolor{\my@color}
    \iftikz@mode@fill
      \pgfsetfillcolor{\my@color}
      \pgfusepath{stroke,fill}
    \else
    \iftikz@mode@draw
    \pgfusepath{stroke}
    \fi
    \fi

   \endgroup
 }
}

%
% This holds our state widths for the decoration so that we
% can vary them each time.
%
\newdimen\flower@sep
\flower@sep=1cm\relax

\pgfdeclaredecoration{flowers}{initial}{
  \state{initial}[width=\flower@sep]
{
\pgfmathparse{round(rnd*90)}
\pgftransformrotate{\pgfmathresult}
\pgfmathparse{0.2+rand*0.1}
\pgftransformscale{\pgfmathresult}
\pgfsetlinewidth{\pgfmathresult*\pgflinewidth}
%
% These should be customisable, I guess.
%
\def\tikz@strokecolor{purple}
\def\tikz@fillcolor{pink}
\tikz@mode@drawtrue
\tikz@mode@filltrue
\pgfnode{flower}{center}{}{}{}
%
% Reset the width
%
\pgfmathsetlength\flower@sep{1cm + rand*0.5cm}
\global\flower@sep=\flower@sep
}
\state{final}
{
  \pgfpathmoveto{\pgfpointdecoratedpathlast}
}
}


\makeatother

\begin{document}

\tikz \node[flower, fill=pink, draw=purple] {};

\tikz \node[flower, fill, color=red] {};

\tikz \node[flower, draw=red] {};


\tikz \draw[decorate, decoration={flowers}] (-4,-3) -- (4,-3);

\end{document}

More flowers