4

Consider the following LaTeX manuscript featuring a TikZ picture of a tall, rainbow shaded rectangle. The code is essentially copy-and-pasted from the example in the end of subsection 109.3 ('Using Shadings') of chapter 109 ('Shadings') of the TikZ & PGF manual for version 3.0.1a, p. 1088.

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
    \pgfdeclareverticalshading{rainbow}{100bp}{color(0bp)=(red); color(25bp)=(red); color(35bp)=(yellow); color(45bp)=(green); color(55bp)=(cyan); color(65bp)=(blue); color(75bp)=(violet); color(100bp)=(violet)}
    \begin{tikzpicture}[shading=rainbow]
        \shade[shading angle=90] (3,0) rectangle +(1,2);
    \end{tikzpicture}
\end{document}

The resulting image is (not to scale)

                                                  A rainbow shaded rectangle

The shading of this rectangle ranges from violet on the left to red on the right. I don't understand why this is so. In my opinion it should range from blue to yellow. I base this opinion on the shading algorithm given in the description of the \pgfshadepath{〈shading name〉}{〈angle〉} command on pp. 1085-1086:

First, PGF will set up a local scope.

Second, it uses the current path to clip everything inside this scope. However, the current path is once more available after the scope, so it can be used, for example, to stroke it.

Now, the 〈shading name〉 should be a shading whose width and height are 100 bp, that is, 100 big points. PGF has a look at the bounding box of the current path. This bounding box is computed automatically when a path is computed; however, it can sometimes be (quite a bit) too large, especially when complicated curves are involved.

Inside the scope, the low-level transformation matrix is modified. The center of the shading is translated (moved) such that it lies on the center of the bounding box of the path. The low-level coordinate system is also scaled such that the shading “covers” the path (the details are a bit more complex, see below). Then, the coordinate system is rotated by 〈angle〉.

Finally, if the macro \pgfsetadditionalshadetransform has been used, an additional transformation is applied.

After everything has been set up, the shading is inserted.

If both the path and the shadings were always rectangles and if rotations were never involved, it would be easy to scale shadings such they always cover the path. However, when a vertical shading is rotated, it must obviously be “magnified” so that it still covers the path. Things get worse when the path is not a rectangle itself.

For these reasons, things work slightly differently “in reality.” The shading is scaled and translated such that the point (50bp,50bp), which is the middle of the shading, is at the middle of the path and such that the point (25bp,25bp) is at the lower left corner of the path and that (75bp,75bp) is at upper right corner.

To reiterate, the essential steps are:

  1. Create a shaded square of size 100bpx100pb based on the \pgfdeclareverticalshading specifications. I shall refer to this square as the background square.
  2. Shift the background square so that its center coincide with the center of the bounding box of the shape-to-be-shaded.
  3. Scale the shifted background square, so that its sub-square, whose bottom-left corner is at (25pb,25pb) and whose upper-right corner is at (75bp,75bp) (coordinates before the shift), coincides with the bounding box of the shape-to-be-shaded.
  4. Rotate the shifted and scaled background square by the specified angle.
  5. Clip the resulting background square as per the shape-to-be-shaded.

In particular, the rotation of the background square (step 4) is performed after it has been shifted and scaled (step 3).

Let's apply this to our example. Just before being rotated, the background square is transformed in such a way that the color that runs along the top of the shape-to-be-shaded is violet, and the color that runs along the bottom of the shape-to-be-shaded is red. Now the 90 degree rotation takes place, and, since the shape-to-be-shaded is a tall, thin rectangle, the violet and red colors of the rotated background square would fall outside the shape-to-be-shaded. Hence, after the clipping only the blue to yellow spectrum should remain visible.

If the rotation were the first transformation to be carried out, i.e. if steps 2, 3 & 4 of the algorithm were permuted as follows: 2->3, 3->4, 4->2, then the image obtained above would make sense to me.

1
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Joseph Wright
    Commented Jul 28, 2017 at 9:30

2 Answers 2

3

Shades the current path, but does not discard it.

#1 - a shading (see below)

#2 - an angle

Description:

\pgfshadepath "tries" to fill the current path with a shading. The shading's original size should completely cover the area between (0,0) and (100bp,100bp). The shading will be rotated by #2 and then rescaled so that it completely covers the path. Then the path will be used (locally) for clipping and the shading is drawn.

In addition to the rotation, any transformation set by the \pgfsetadditionalshadetransform will also be applied.

After all this, the path can still be used for the normal stroking/clipping operations.

The shading is rotated around its middle. If no rotation occurs, the lower left corner of the path will lie on (25bp, 25bp), the upper right corner on (75bp, 75bp).

Example:

\pgfdeclareverticalshading{myshading}{100bp}{color(0pt)=(red); color(100bp)=(green)}

\pgfpathmoveto{\pgforigin}
\pgfpathlineto{\pgfxy(1,0)}
\pgfpathlineto{\pgfxy(1,1)}
\pgfshadepath{myshading}{0}
\pgfusepath{stroke}

--- Excerpt from tex/generic/pgf/basiclayer/pgfcoreshade.code.tex.

4
  • According to the description you quoted "The shading will be rotated by #2 and then rescaled so that it completely covers the path." So rotation comes before rescaling. This makes sense, at last! As a matter of fact, I suggested this in the last paragraph of my original post. However, according to Symbol 1's answer, which is based on the source code, rotation comes after shifting and scaling. How can I reconcile these answers?
    – Evan Aad
    Commented Jul 25, 2017 at 16:45
  • @EvanAad They aren't at odds. The rotation is done after the scaling, but it really doesn't matter. At least, I don't think it matters because it is not done like that. It is set up and then it is done afterwards. So it all gets set up and then everything gets done. It is like: you say \pgflineto and then \pgfusepath{stroke}, so the line is set up, then the stroking. But not one and then the other. One is set up and then the other. But not one is done and then the other.
    – cfr
    Commented Jul 25, 2017 at 18:28
  • @EvanAad See page 1103.
    – cfr
    Commented Jul 25, 2017 at 18:34
  • Now I know how to reconcile your answer with Symbol 1's. To calculate the absolute coordinates of a point p, whose coordinates are given in a coordinate system resulting from a sequence of transformations, the transformation matrices should be applied to p's relative coordinates in reverse. Thus, since the sequence of coordinate-system transformations specified in the source code is: 1. shift, 2. scale, 3. rotate, the absolute coordinates of a point are calculated by applying the transformations in reverse: 1. rotate, 2. scale, 3. shift.
    – Evan Aad
    Commented Jul 26, 2017 at 10:51
6

Your question is best answered by the source code

\def\pgfshadepath#1#2{%
  \ifdim\pgf@pathminx=16000pt%
    \pgfwarning{No path specified that can be filled}%
  \else%
    \begingroup%  
      % Calculate center:
      \pgf@xb=.5\pgf@pathmaxx%
      \advance\pgf@xb by.5\pgf@pathminx%
      \pgf@yb=.5\pgf@pathmaxy%
      \advance\pgf@yb by.5\pgf@pathminy%
      % Calculate scaling:
      \pgf@xc=\pgf@pathmaxx%
      \advance\pgf@xc by-\pgf@pathminx%
      \pgf@yc=\pgf@pathmaxy%
      \advance\pgf@yc by-\pgf@pathminy%
      \pgf@xc=.01992528\pgf@xc%
      \pgf@yc=.01992528\pgf@yc%
      \ifdim\pgf@xc<0.0001pt\relax\ifdim\pgf@xc>-0.0001pt\relax\pgf@no@shadetrue\fi\fi%
      \ifdim\pgf@yc<0.0001pt\relax\ifdim\pgf@yc>-0.0001pt\relax\pgf@no@shadetrue\fi\fi%
      \ifpgf@no@shade\else%
      \pgfsys@beginscope%
        \pgfsyssoftpath@invokecurrentpath%
        \pgfsys@clipnext%
        \pgfsys@discardpath%
        % Compute new transformation matrix:
        \pgfsys@transformcm{1}{0}{0}{1}{\pgf@xb}{\pgf@yb}%
        \pgfsys@transformcm%
        {\pgf@sys@tonumber{\pgf@xc}}{0}%
        {0}{\pgf@sys@tonumber{\pgf@yc}}{0pt}{0pt}%
        \pgfmathparse{#2}%
        \let\pgfshade@angle=\pgfmathresult%
        \pgfmathsin@{\pgfshade@angle}%
        \let\pgfshade@sin=\pgfmathresult%
        \pgfmathcos@{\pgfshade@angle}%
        \let\pgfshade@cos=\pgfmathresult%
        \pgf@x=\pgfshade@sin pt%
        \pgf@xa=-\pgf@x%
        \pgfsys@transformcm{\pgfshade@cos}{\pgfshade@sin}{\pgf@sys@tonumber{\pgf@xa}}{\pgfshade@cos}{0pt}{0pt}%
        \ifx\pgf@shade@extra@transform\pgfutil@empty%
        \else%
          \pgflowlevel{\pgf@shade@extra@transform}%
        \fi%
        \pgfuseshading{#1}%
      \pgfsys@endscope%
      \fi%  
    \endgroup%
  \fi%
}
  • #1 is the name of shading; rainbow in your case.
  • #2 is the angle; 90 in your case.
  • (\pgf@xb,\pgf@yb) is the center of the (BB of the) path.
  • (\pgf@xc,\pgf@yc) is the size of the (BB of the) path; unit = 50bp.
    • .01992528 * 50 bp = 1pt
  • The order of transformation is
    • shift
    • scale
    • rotate

Now for simplicity assume that we have a square. By a scaling we transform it into a thin-tall rectangle.

So the problem is, if we rotate this thin-tall rectangle by 90 degrees, is it going to be a thin-tall rectangle, or a wide-short rectangle. The answer is the former. Try, for example,

\makeatletter
\tikz{
    \path(-3,-3)(3,3);
    \pgfsys@transformcm{.5}{0}{0}{2}{0pt}{0pt}
    \pgfsys@transformcm{0}{1}{-1}{0}{0pt}{0pt} % comment and uncomment this line
    \node[draw]{};                             % and see what happens to this *square*
    \node{ABC};
}

PGF has nothing to do with this behavior. It is all about PDF standard. Perhaps the following snapshot of PDF specification helps you

15
  • Thanks. Let me see if I understand your message. Please correct me if I'm wrong. (1) The various transformations of 〈shading name〉 mentioned in the shading algorithm in the manual are system layer transformations; not basic layer transformations. (2) System layer rotation behaves differently than basic layer rotation. Specifically, a system layer rotation sometimes involves not only a rotation, but also a scaling; in particular, if a rectangle is rotated by 90 degrees about its center using the system-layer, the resulting rectangle's circumference will coincide with the original rectangle.
    – Evan Aad
    Commented Jul 25, 2017 at 14:45
  • (continued) and the interior of the rotated rectangle, if not empty, will be scaled to fit in its entirety inside the circumference of the original rectangle. (3) The system layer inherits this behavior from the PDF specifications.
    – Evan Aad
    Commented Jul 25, 2017 at 14:48
  • Furthermore, I imagine the vast majority of questions on this forum could have been given the answer: "Just read the source code", which would render this forum unnecessary. I personally find it difficult to read TeX source code, and I think it's fair to expect an answer to be in the form of an explanation in plain English rather than a referral to the source code.
    – Evan Aad
    Commented Jul 25, 2017 at 16:16
  • @EvanAad Did you even look at the source file I mentioned? Did you read the description of \pgfshadepath there? I don't know how much plainer English you expect to get.
    – cfr
    Commented Jul 25, 2017 at 16:31
  • 2
    @EvanAad Technically your question has to be old enough to be eligible for a bounty. (two days or so.) By the way, 50 reps or even 0 reps are totally fine. I offered 500 reps because (a) I am reaching 20.5k but anything more than 20k is useless and (b) I can answer the MSE question by myself in an emergency case. I cannot lose =P.
    – Symbol 1
    Commented Jul 26, 2017 at 13:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .