8

I want to magnify part of a picture with the TikZ spy library; however, with a scope for using relative coordinates like in #9561 Drawing on an image with TikZ. Provided I'm using the image tux.png - consider this MWE:

\documentclass[%
  12pt,
  journal,
  onecolumn,
  twoside,
  draftcls,
  letterpaper,
]{IEEEtran}

% wget https://i.stack.imgur.com/aVGcn.png -O tux.png

\usepackage[nopar]{lipsum}
\usepackage{xstring}
% https://tex.stackexchange.com/a/26808/2595
\makeatletter
\def\unpacklipsum#1#2#3{%
  \count@=#1\relax
  \advance\count@\m@ne
  \def#3{}%
  \loop\ifnum\count@<#2\relax
    \advance\count@\@ne
    \edef#3{#3\csname lipsum@\romannumeral\count@\endcsname}%
  \repeat}
\makeatother
% https://tex.stackexchange.com/a/168754/2595
\def\loremnchars[#1]#2{%
  \unpacklipsum{#1}{#1}{\myunpacked}%
  \StrMid{\myunpacked}{1}{#2}% same as \StrLeft{\myunpacked}{#2}
}


\usepackage{caption}
\usepackage{tikz}
\usetikzlibrary{shapes}
\usetikzlibrary{spy}

\begin{document}

\begin{center}
  \begin{tikzpicture}[
    spy using outlines={%
      draw=red,
      ellipse,
      red,
      magnification=2,
      connect spies
    }
  ]
    \node [draw=black, anchor=south west, inner sep=0pt] (myimg) at (0,0) {
      \includegraphics[width=0.9\textwidth]{tux.png}
    };
    \begin{scope}[
      x={(myimg.south east)},y={(myimg.north west)},
      spy using outlines={%
        draw=red,
        ellipse,
        red,
        magnification=2,
        connect spies
      }
    ]
      \draw[help lines,xstep=.1,ystep=.1] (0,0) grid (1,1);
      \node[
        draw=red,ellipse,line width=1pt,minimum width=0.9cm,minimum height=5.25cm
      ]
      at (0.53,0.7) {};
      \spy[
        draw=red,ellipse,line width=1pt,minimum width=0.9cm,minimum height=5.25cm
      ]
      on (0.53,0.7) in node [left] at (0.8,0.825);
    \end{scope}
  \end{tikzpicture}
  \captionof{figure}[justmy]{Just my image; \loremnchars[5]{255} ...}
  \label{fig:test}
\end{center}

\end{document}

It produces this image:

test71.png

The first \node ... at (0.53,0.7) is merely to indicate the area that I want to be spied on. Two problems are visible:

  • The same settings as used for that \node, when applied to a \spy, result with an ellipse smaller than the originally requested one
  • Apparently, since a scope is used, the \spy doesn't "see" any graphics (more precisely, image pixels) "below", so it seemingly only zooms in the grid.

Can I use this kind of a setup of \includegraphics and scope inside a tikzpicture, but still obtain a proper \spy, expressed in relative coordinates - and if yes, how?

2 Answers 2

5

Ok, think I got something for now - hopefully someone will post a more correct answer eventually, though. Anyways, with the code below, I now get this:

test72.png

Caveats:

  • While \node can (and does) adjust itself to the relative coordinates as per the scope, \spy apparently cannot (and doesn't)
    • However, \spy can refer to nodes, so we can set named \node and/or \coordinate (in respect to the scope relative coordinates), and feed them as inputs to \spy just fine
  • Apparently, the size options entered for \spy are for the final, the "in node" display - not for the "peeker" on (node); so do modify the size of the "peeker", we have to multiply the size options by the magnification
  • Only include spy using outlines on the element/"layer" that is being spied (i.e. if it is added to the scope, then the pixels of the image will be unavailable; it should only be added to the tikzpicture, because it is the one "hosting" the \includegraphics and its pixels).

With that in mind, below is the modified portion of the MWE in OP that makes it work (only the document part without preamble, as changes are made only there). Note that \magnif is where the magnification is now stored; and since \spy seems to choke on expandability of lengths, lengths are \def'd instead (and \global\def'd at that, otherwise \spy cannot see them); and finally, with all this, now I have no idea what controls the line width of the "peeker" anymore (but I can control the line width of the indicator node, which works just as well). Anyways, here's the code:

\begin{document}

\global\def\magnif{2}

\begin{center}
  \begin{tikzpicture}[
    spy using outlines={%
      draw=blue,
      ellipse,
      red,
      magnification=\magnif,
      line width=2pt,
      connect spies
    }
  ]
    \node [draw=black, anchor=south west, inner sep=0pt] (myimg) at (0,0) {
      \includegraphics[width=0.9\textwidth]{tux.png}
    };
    \begin{scope}[
      x={(myimg.south east)},y={(myimg.north west)},
      % enabling spy using outlines here makes the pixels unreadable; so skip it:
    ]
      \draw[help lines,xstep=.1,ystep=.1] (0,0) grid (1,1);
      % must have \global for \spy!
      % lengths may not be expandable: http://tex.stackexchange.com/a/18302/2595
      % better use \edef here, in case you need {0.5\somelength}, else \spy will not pick it up
      \global\edef\mw{0.9cm} %\newlength{\mw}\setlength{\mw}{0.9cm}
      \global\edef\mh{5.25cm} %\newlength{\mh}\setlength{\mh}{5.25cm}
      \node[
        draw=red,ellipse,line width=1pt,minimum width=\mw,minimum height=\mh,
      ]
      (lookhere) at (0.53,0.7) {};
      \coordinate (lookherenext) at (0.8,0.825);
      % line width here has no effect;
      % also, coordinates for \spy are not scaled!
      \spy[
        draw=green,ellipse,line width=4pt,minimum width=\magnif*\mw,minimum height=\magnif*\mh,
      ]
      %on (0.53,0.7) in node [left] at (0.8,0.825);
      on (lookhere) in node [left] at (lookherenext);
    \end{scope}
  \end{tikzpicture}
  \captionof{figure}[justmy]{Just my image; \loremnchars[5]{255} ...}
  \label{fig:test}
\end{center}

\end{document}
1
  • THanks, I ran into the same problem, when using a similar idea in beamer and there - your solution also applies.
    – Ronny
    May 9, 2014 at 9:39
1

I am not a PGF guru, but I think your self-proposed solution is not that far from being complete. I would make it just a bit easier to be used, i.e.

  • adjust the scope new coordinate system to let it work also when referring to nodes not placed at (0,0) and,
  • more important, let specify the size of the to-be-magnified area using the same unit of the scope coordinate system, namely "pure" numbers (generally 0 to 1).

Here a MWE built from your answer. Note that a I changed a bit the coordinates where to spy and width and height of the area to spy, in order to make more immediate and self-explanatory the meaning of these sizes.

\documentclass[tikz, border=1mm]{standalone}
\usetikzlibrary{calc, shapes, spy}

\begin{document}
    \begin{tikzpicture}[spy using outlines={%
                            ellipse,
                            red,
                            line width=2pt,
                            connect spies
                        }]
        \node[inner sep=0mm] (fig) at (0,0){\includegraphics{tux}};
        %Scope relative to the image to be spied on
        \begin{scope}[x={($(fig.south east) - (fig.south west)$)},
                      y={($(fig.north west) - (fig.south west)$)}, shift={(fig.south west)}]
            %Extract units in x and y directions as length
            \path let \p{P}=(1,0), \n{xlencm}={scalar(veclen(\x{P},\y{P})/1cm)}
                  in \pgfextra{\xdef\ux{\n{xlencm}cm}};
            \path let \p{P}=(0,1), \n{ylencm}={scalar(veclen(\x{P},\y{P})/1cm)}
                  in \pgfextra{\xdef\uy{\n{ylencm}cm}};
            %Add a grid to pick up areas to zoom on
            \draw[help lines, step=0.1] (0,0) grid (1.0001,1.0001);
            \coordinate (lookherenext) at (0.6,0.3);
            \coordinate (lookhere) at (0.5,0.7);
            %Auxiliary macros
            \gdef\magnif{2}
            \gdef\mw{0.4} % These are now in the figure coordinate
            \gdef\mh{0.2} % system, which makes it easier to spy
            %Actual spy command
            \spy[magnification=\magnif,
                 minimum width=\magnif*\mw*\ux,
                 minimum height=\magnif*\mh*\uy] on (lookhere) in node at (lookherenext);
        \end{scope}
    \end{tikzpicture}
\end{document}

MWE-basic

Extracting the units in x and y direction was made using the calc library using this technique.

About the fact you cannot directly use (i.e. hard-code) your new coordinate system in the \spy on coordinates, this is related to how \spy works and this answer shows that it is "standard" to define new named \coordinates to use in the \spy command.

Also note that I did not bother to adjust the new scope coordinate system definition to the general case where the fig node has a non-zero inner separation and the user would like to have (0,0) and (1,1) to the bottom-left and top-right corners of the included graphic, only.

Bonus track

If you need this on a more regular basis (e.g. in a beamer presentation to show different magnified parts on different slides/overlays), I would extract code into commands/environments. With a bit of imagination you can get as far as you wish. The following might serve as a starting point, where I used xparse and etoolbox to define a new environment meant to be used inside a tikzpicture and a new command meant to be used inside the custom environment. The environment has a * variant to draw guidelines on the node you want to spy on. It is meant to be used when "drawing" and then meant to be removed once the spying is ready.

\documentclass[tikz, border=1mm]{standalone}
\usepackage{xparse, etoolbox}
\usetikzlibrary{calc, shapes, spy}

\makeatletter
%Tikz scope to spy on node with relative coordinates
\NewDocumentEnvironment{internal@SpyOnNode}{ s m O{} }
{
    \begin{scope}[x={($(#2.south east) - (#2.south west)$)},
                  y={($(#2.north west) - (#2.south west)$)}, shift={(#2.south west)}, #3]
        \IfBooleanTF{#1}
        {
            \draw[help lines, xstep=0.05,ystep=0.05] (0,0) grid (1.001,1.001);
            \draw[very thin,  xstep=0.1,ystep=0.1] (0,0) grid (1.001,1.001);
            \pgfkeys{/pgf/number format/precision=1}
            \foreach \x in {0,0.1,...,1.001} {
                \node [anchor=north, font=\tiny] at (\x,0) {\rotatebox{-90}{\pgfmathroundtozerofill{\x}\pgfmathresult}};
                \node [anchor=south, font=\tiny] at (\x,1) {\rotatebox{90}{\pgfmathroundtozerofill{\x}\pgfmathresult}};
                \node [anchor=east, font=\tiny]  at (0,\x) {\pgfmathroundtozerofill{\x}\pgfmathresult};
                \node [anchor=west, font=\tiny]  at (1,\x) {\pgfmathroundtozerofill{\x}\pgfmathresult};
            }
        }{}
        %Extract units in x and y directions as length
        \path let \p{P}=(1,0), \n{xlencm}={scalar(veclen(\x{P},\y{P})/1cm)}
              in \pgfextra{\xdef\ux{\n{xlencm}cm}};
        \path let \p{P}=(0,1), \n{ylencm}={scalar(veclen(\x{P},\y{P})/1cm)}
              in \pgfextra{\xdef\uy{\n{ylencm}cm}};
}
{
    \end{scope}
}
\NewDocumentEnvironment{SpyOnNode}{ m O{} }
  {\begin{internal@SpyOnNode}{#1}[#2]}
  {\end{internal@SpyOnNode}}
\NewDocumentEnvironment{SpyOnNode*}{ m O{} }
  {\begin{internal@SpyOnNode}*{#1}[#2]}
  {\end{internal@SpyOnNode}}

%Command meant to be used in the environment above (\ux and \uy defined)
% #1 -> magnification factor
% #2 -> width  of area to be magnified
% #3 -> height of area to be magnified
% #4 -> name of the coordinate at which to spy on
% #5 -> name of the coordinate at which the magnification is placed
\NewDocumentCommand{\spyarea}{m m m m m }{%
    \spy[magnification=#1,
         minimum width=#1*#2*\ux,
         minimum height=#1*#3*\uy] on (#4) in node at (#5);
}
\makeatother


\begin{document}
    \begin{tikzpicture}[spy using outlines={%
                            ellipse,
                            red,
                            line width=2pt,
                            connect spies
                        }]
        \node[inner sep=0mm] (fig) at (0,0){\includegraphics{tux}};
        \begin{SpyOnNode*}{fig} % Remove the * when the picture is finalised
            \path coordinate (from1) at (0.50,0.70)
                  coordinate (from2) at (0.15,0.35)
                  coordinate (to1)   at (0.6,0.2)
                  coordinate (to2)   at (0.1,0.7);
            \spyarea{2}{0.4}{0.2}{from1}{to1}
            \spyarea{2}{0.1}{0.1}{from2}{to2}
        \end{SpyOnNode*}        
    \end{tikzpicture}
\end{document}

MWE-advanced

De gustibus

In the above code I chose not to delegate the creation of the coordinates to be spied on/to to the \spyarea command, since you might want to reuse those coordinates later. If this is not your need, the version below is even easier to use as the creation of the coordinates is hidden inside the command. However, to do so, the name of those coordinates has to change at every call, because

The \spy command does not create the nodes immediately. Rather, the creation of these nodes is postponed till the end of the spy scope in which the \spy command is used. This is necessary since in order to repeat the whole scope inside the node containing the magnified version, the whole picture needs to be available when this node is created.

For this purpose, I used a PGF random number, but e.g. a counter could also have been used. Furthermore, it is crucial to issue the \spy command with the names of the coordinates completely expanded when \spy gets expanded. To know more about the used technique you can refer to this useful answer.

\documentclass[tikz, border=1mm]{standalone}
\usepackage{xparse, etoolbox}
\usetikzlibrary{calc, shapes, spy}

\makeatletter
%Tikz scope to spy on node with relative coordinates
\NewDocumentEnvironment{internal@SpyOnNode}{ s m O{} }
{
    \begin{scope}[x={($(#2.south east) - (#2.south west)$)},
                  y={($(#2.north west) - (#2.south west)$)}, shift={(#2.south west)}, #3]
        \IfBooleanTF{#1}
        {
            \draw[help lines, xstep=0.05,ystep=0.05] (0,0) grid (1.001,1.001);
            \draw[very thin,  xstep=0.1,ystep=0.1] (0,0) grid (1.001,1.001);
            \pgfkeys{/pgf/number format/precision=1}
            \foreach \x in {0,0.1,...,1.001} {
                \node [anchor=north, font=\tiny] at (\x,0) {\rotatebox{-90}{\pgfmathroundtozerofill{\x}\pgfmathresult}};
                \node [anchor=south, font=\tiny] at (\x,1) {\rotatebox{90}{\pgfmathroundtozerofill{\x}\pgfmathresult}};
                \node [anchor=east, font=\tiny]  at (0,\x) {\pgfmathroundtozerofill{\x}\pgfmathresult};
                \node [anchor=west, font=\tiny]  at (1,\x) {\pgfmathroundtozerofill{\x}\pgfmathresult};
            }
        }{}
        %Extract units in x and y directions as length
        \path let \p{P}=(1,0), \n{xlencm}={scalar(veclen(\x{P},\y{P})/1cm)}
              in \pgfextra{\xdef\ux{\n{xlencm}cm}};
        \path let \p{P}=(0,1), \n{ylencm}={scalar(veclen(\x{P},\y{P})/1cm)}
              in \pgfextra{\xdef\uy{\n{ylencm}cm}};
}
{
    \end{scope}
}
\NewDocumentEnvironment{SpyOnNode}{ m O{} }
  {\begin{internal@SpyOnNode}{#1}[#2]}
  {\end{internal@SpyOnNode}}
\NewDocumentEnvironment{SpyOnNode*}{ m O{} }
  {\begin{internal@SpyOnNode}*{#1}[#2]}
  {\end{internal@SpyOnNode}}

%Command meant to be used in the environment above (\ux and \uy defined)
% #1 -> magnification factor
% #2 -> width  of area to be magnified
% #3 -> height of area to be magnified
% #4 -> coordinates at which to spy on
% #5 -> coordinates at which the magnification is placed
\NewDocumentCommand{\spyarea}{m m m m m}{%
    \pgfmathgeneratepseudorandomnumber
    \let\myprn\pgfmathresult
    \coordinate (tmp@from@\myprn) at #4;
    \coordinate (tmp@to@\myprn) at #5;
    \expanded{%
        \noexpand\spy[magnification=#1,
                      minimum width=#1*#2*\ux,
                      minimum height=#1*#3*\uy] on (tmp@from@\myprn) in node at (tmp@to@\myprn);
     }
}
\makeatother


\begin{document}
    \begin{tikzpicture}[spy using outlines={%
                            ellipse,
                            red,
                            line width=2pt,
                            connect spies
                        }]
        \node[inner sep=0mm] (fig) at (0,0){\includegraphics{tux}};
        \begin{SpyOnNode*}{fig} % Remove the * when the picture is finalised
            \spyarea{2}{0.4}{0.2}{(0.50,0.70)}{(0.6,0.2)}
            \spyarea{2}{0.1}{0.1}{(0.15,0.35)}{(0.1,0.7)}
        \end{SpyOnNode*}        
    \end{tikzpicture}
\end{document}

You must log in to answer this question.

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