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.

Evan Aad
  • 11,066

2 Answers2

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

Symbol 1
  • 36,855
  • 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 Jul 25 '17 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 Jul 25 '17 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 Jul 25 '17 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 Jul 25 '17 at 16:31
  • Symbol 1, you stated: "So the problem is, if we rotate a thin-tall rectangle by 90 degrees, is it going to be a thin-tall rectangle, or a wide-short rectangle." I agree with you: this is the problem. Then you answered: "The answer is the former." In other words, you're saying that if we rotate a thin-tall rectangle by 90 degrees, it is going to remain a thin-tall rectangle. But this is not true. – Evan Aad Jul 25 '17 at 20:14
  • I made an experiment, which you can see in the end of my answer, and the result of this experiment is that a thin-tall rectangle that is rotated by 90 degrees (with system layer commands) becomes a wide-short rectangle. So your answer doesn't solve my question, it just reaffirms it. – Evan Aad Jul 25 '17 at 20:14
  • And, by the way, I know it's not your fault, but the picture you copied from the PDF specifications is erroneous: there's no way in hell that step #2 on the second line describes a rotation. Rotation is an orthogonal transformation, and as such preserves angles. – Evan Aad Jul 25 '17 at 20:39
  • @EvanAad It seems like you study math a lot. So let me put it this way: you have to figure out whether \pgfsys@transformcm is a left group action or right group action. – Symbol 1 Jul 25 '17 at 20:57
  • And maybe... just maybe... you can forward the snapshot to math.stackexchange and ask whether you or the PDF specification is correct. I bet 500 reputation it is the later. But they will tell you how and why. – Symbol 1 Jul 25 '17 at 21:02
  • Your bet is on, Symbol 1! – Evan Aad Jul 25 '17 at 21:03
  • Here's the link to math.stackexchange post. – Evan Aad Jul 25 '17 at 21:14
  • OK, I stand corrected. I don't know how to transfer reputation points, but at any rate I wouldn't give you 500 points but the relative amount considering my and your total reputations in this forum as of now, so this would be (500/20439)*1470, i.e. roughly 36 reputation points, rounded up. If the mods can transfer 50 reputation points from me to you they have my blessing. – Evan Aad Jul 26 '17 at 10:07
  • @EvanAad That seems to be back-tracking somewhat. Nothing about relative worth was mentioned when you originally accepted the bet. To transfer points, you can add a bonus to your question and then award it to this answer. Just make clear in the reason for the bonus that the points are already awarded, so to speak, so people don't spend time trying to earn them. – cfr Jul 26 '17 at 10:59
  • @cfr: Everything in life is relative. Even in TikZ, the coordinate (500,1) can only be understood relative to the current transformation matrix. At any rate, 50 points is all I'll give and I consider even this to be 14 points too many. How can I give a bonus to my question? – Evan Aad Jul 26 '17 at 12:42
  • 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 Jul 26 '17 at 13:20
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.

cfr
  • 198,882
  • 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 Jul 25 '17 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 Jul 25 '17 at 18:28
  • @EvanAad See page 1103. – cfr Jul 25 '17 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 Jul 26 '17 at 10:51