14

I am brand new to TikZ, so please forgive any foolishness I express in this post.

Gimp has a very nice perspective transform tool, which shows a 3x3 matrix with values that change as you drag using the tool. An example of me using this GIMP tool is shown in the images at the bottom of this post (from top to bottom, the images are a circle that I drew, a perspective-transformed circle, and a screenshot of the GIMP tool I used to transform the circle.

Does anyone know if there is a similar tool, or know how to code one, in TikZ? Ideally I am looking for something that would be of the form

\perspectiveTransform{<shape name or draw command>}{<perspective transform matrix parameters>}

(If there is a tool that does just this that takes a different format, that would be great too.)

So, an example that does what is done in the pictures at the bottom of this post might be something like:

\perspectiveTransform{
    \draw[line width = 10.0pt] (0,0) circle (1); %or name of shape
}
{
    1.0, 1.4, 0.0
    0.0, 1.4, 0.0
    0.0, 0.004, 1.0;
    %these are roughly the matrix values that showed up while using the GIMP tool.
}

I've looked at a few posts trying to figure out how to do this - the one I've found that seems to come closest to what I'm looking for is here: Perspective transform. I don't really understand the code in this example though, including whether it is made for just this example, or whether it can be employed as a general tool, like I'm looking for.

Thanks for any help! Here are the images I mentioned:

enter image description here

enter image description here

enter image description here

6
  • 1
    Doesn't that just amount to a change in the coordinate system?
    – JPi
    Jul 13, 2016 at 0:44
  • 1
    If that's not enough, tikz-3dplot but for simple cases, the coordinate/canvas transformations would be enough. (GIMP doesn't do 3D any more than TikZ.)
    – cfr
    Jul 13, 2016 at 1:02
  • 1
    Please provide a minimal working example to use as a basis for answers, regardless.
    – cfr
    Jul 13, 2016 at 1:32
  • 1
    Two related questions: tex.stackexchange.com/questions/301897/star-wars-text-effect and tex.stackexchange.com/questions/313880/…. In the star-wars answer, each letter is isometrically slanted, but the differing slants on each letter gives the appearance of perspective. In the valley text answer, that could be used for true perspective, if the parabolic function is changed to a linear one (but it is computationally costly). Jul 13, 2016 at 10:24
  • 1
    Are you interested in a solution in metapost? I wrote some metapost code eons ago that could apply arbitrary transformations to arbitrary metapost pictures, using metapost's ability to decompose a picture after it has been drawn. The picture could contain blobs of TeX. The transformations did not have to be linear or affine, and I remember doing perspective transformations. I think I had to hard-code the derivative matrix for a given transformation, so that it could be approximated by an affine transformation for the individual glyphs. Jul 13, 2016 at 14:30

4 Answers 4

18

At a ridiculous computational cost, using a linear variation of my answer at Draw Text in different shapes, with grid supplied by Drawing minimal xy axis.

As written, the vanishing point cannot be directly above the object, but I would image with clever use of \rotatebox before and after the transformation, it could be obtained.

REVISED SOLUTION (vertical plus depth foreshortening)

I realized my original solution (below) foreshortened the vertical measure of the object, but did nothing to foreshorten the object along the line to the vanishing point. This often is unnoticeable, until the object is rendered close to the vanishing point. Then, it becomes clear.

So, in this revision, I foreshorten both the height and depth of the object (this is most obvious in the right hand image of the 2nd row of transformed images). I also reduced the slices to 150, because otherwise, I overflow some LaTeX or PDF limit.

\documentclass{article}
\usepackage{ifthen,trimclip,calc,fp,graphicx,xcolor}
\newsavebox\mytext
\newcounter{mycount}
\newlength\clipsize
\newcommand\parabtext[5][0]{%
  \edef\neck{#3}% percent to depress the amplitude
  \def\cuts{#4}% Number of cuts
  \savebox{\mytext}{\kern.2pt#5\kern.2pt}% TEXT
  \FPeval{\myprod}{1/cuts}%
  \clipsize=\myprod\wd\mytext\relax%
  \setcounter{mycount}{0}%
  \whiledo{\value{mycount}<\cuts}{%
    \stepcounter{mycount}%
    \edef\NA{\themycount}%
    \edef\NB{\the\numexpr\cuts-\themycount\relax}%
    \FPeval{\myprod}{\NA/\cuts}%
    \ifnum0#1=0\relax%
      \FPeval{\myprod}{1 - \neck*(\myprod)}%
    \else%
      \FPeval{\myprod}{1 - \neck*(1-\myprod)}%
    \fi%
    \FPmul{\myprodB}{\myprod}{\myprod}%
    \scalebox{\myprod}[1]{\clipbox{%
      \value{mycount}\clipsize\relax{} %
      -1pt %
      \wd\mytext-\value{mycount}\clipsize-\clipsize\relax{} %
      -1pt%
    }{\raisebox{#2\dimexpr\ht\mytext-\myprodB\ht\mytext}{%
        \scalebox{1}[\myprodB]{\usebox{\mytext}}}}%
  }}%
}
%%%%%%%%%%
\usepackage[usestackEOL]{stackengine}
\usepackage{xcolor,graphicx,amssymb}
\setstackgap{L}{1cm}
\def\stacktype{L}
% DASHED LINE OF SPECIFIED LENGTH
% From morsburg at https://tex.stackexchange.com/questions/12537/
% how-can-i-make-a-horizontal-dashed-line/12553#12553
\def\solidfill{\cleaders\hbox to .1cm{\rule{.1cm}{1pt}}\hfill}
\def\dashfill{\cleaders\hbox to .2cm{\rule{.05cm}{.4pt}}\hfill}
\newcommand\dashline[1]{\hbox to #1{\dashfill\hfil}}
\newcommand\solidline[1]{\hbox to #1{\solidfill\hfil}}
\newcommand\DL{\textcolor{black!30}{\dashline{6.6cm}}}
\newcommand\SL{\textcolor{black}{\solidline{6.8cm}}\makebox[.2cm][r]{\arrowhead}}
\def\arrowhead{\raisebox{-2.6pt}{$\blacktriangleright$}}
%%%%%%%%%%
\begin{document}
\savestack\partA{\Longstack{\DL\\ \DL\\ \DL\\ \SL\\ \DL\\ \DL\\ \DL}}
\savestack\X{\stackinset{c}{}{c}{}{\Huge o}{\scalebox{.15}{\stackinset{c}{10pt}{t}{3pt}{$y$}{%
  \stackinset{r}{3pt}{c}{-10pt}{$x$}{%
    \stackon[-.5cm]{\partA}{\rotatebox{90}{\partA}}%
}}}}}
\centering%
%\def\X{\Huge Hot!}
\X\par
\parabtext{0}{.7}{150}{\X}\parabtext[1]{0}{.4}{150}{\X}\par
\parabtext{1.2}{.7}{150}{\X}\parabtext[1]{1.2}{1}{150}{\X}\par
\parabtext{.2}{.9}{150}{\X}\parabtext[1]{.425}{.7}{150}{\X}
\end{document}

enter image description here

ORIGINAL SOLUTION (vertical foreshortening only)

\documentclass{article}
\usepackage{ifthen,trimclip,calc,fp,graphicx,xcolor}
\newsavebox\mytext
\newcounter{mycount}
\newlength\clipsize
\newcommand\parabtext[5][0]{%
  \edef\neck{#3}% percent to depress the amplitude
  \def\cuts{#4}% Number of cuts
  \savebox{\mytext}{\kern.2pt#5\kern.2pt}% TEXT
  \FPeval{\myprod}{1/cuts}%
  \clipsize=\myprod\wd\mytext\relax%
  \setcounter{mycount}{0}%
  \whiledo{\value{mycount}<\cuts}{%
    \stepcounter{mycount}%
    \edef\NA{\themycount}%
    \edef\NB{\the\numexpr\cuts-\themycount\relax}%
    \FPeval{\myprod}{\NA/\cuts}%
    \ifnum0#1=0\relax%
      \FPeval{\myprod}{1 - \neck*(\myprod)}%
    \else%
      \FPeval{\myprod}{1 - \neck*(1-\myprod)}%
    \fi%
    \clipbox{%
      \value{mycount}\clipsize\relax{} %
      -1pt %
      \wd\mytext-\value{mycount}\clipsize-\clipsize\relax{} %
      -1pt%
    }{\raisebox{#2\dimexpr\ht\mytext-\myprod\ht\mytext}{%
        \scalebox{1}[\myprod]{\usebox{\mytext}}}}%
  }%
}
%%%%%%%%%%
\usepackage[usestackEOL]{stackengine}
\usepackage{xcolor,graphicx,amssymb}
\setstackgap{L}{1cm}
\def\stacktype{L}
% DASHED LINE OF SPECIFIED LENGTH
% From morsburg at https://tex.stackexchange.com/questions/12537/
% how-can-i-make-a-horizontal-dashed-line/12553#12553
\def\solidfill{\cleaders\hbox to .1cm{\rule{.1cm}{1pt}}\hfill}
\def\dashfill{\cleaders\hbox to .2cm{\rule{.05cm}{.4pt}}\hfill}
\newcommand\dashline[1]{\hbox to #1{\dashfill\hfil}}
\newcommand\solidline[1]{\hbox to #1{\solidfill\hfil}}
\newcommand\DL{\textcolor{black!30}{\dashline{6.6cm}}}
\newcommand\SL{\textcolor{black}{\solidline{6.8cm}}\makebox[.2cm][r]{\arrowhead}}
\def\arrowhead{\raisebox{-2.6pt}{$\blacktriangleright$}}
%%%%%%%%%%
\begin{document}
\savestack\partA{\Longstack{\DL\\ \DL\\ \DL\\ \SL\\ \DL\\ \DL\\ \DL}}
\savestack\X{\stackinset{c}{}{c}{}{\Huge o}{\scalebox{.15}{\stackinset{c}{10pt}{t}{3pt}{$y$}{%
  \stackinset{r}{3pt}{c}{-10pt}{$x$}{%
    \stackon[-.5cm]{\partA}{\rotatebox{90}{\partA}}%
}}}}}
\centering%
\X\par
\parabtext{0}{.7}{200}{\X}\parabtext[1]{0}{.4}{200}{\X}\par
\parabtext{1.2}{.7}{200}{\X}\parabtext[1]{1.2}{1}{200}{\X}\par
\parabtext{.2}{.9}{200}{\X}\parabtext[1]{.425}{.7}{200}{\X}
\end{document}

enter image description here

enter image description here

The {200} argument to \parabtext (which I should rename \lineartext) is the number of slices taken of the object. One can speed up the compilation by reducing it, but at the cost of resolution, introducing more stair-stepping. I recommend compiling with the slice count set low, until the final output is desired.

For example, reducing it to {20} gives this result:

enter image description here

4
  • Neat! To make this closer to a perspective transformation, you'll need to scale the width of your slices as well. Currently all examples have the 7 vertical lines equally spaced. Jul 13, 2016 at 14:22
  • @AndrewKepert I was busy doing that as you were leaving your comment. Please see revision. Jul 13, 2016 at 14:25
  • 2
    Check by putting diagonal lines through your square. They should come out straight. In the revised solution if you trace the diagonals of the quadrilaterals in your grid, they don't follow a straight line. Jul 13, 2016 at 14:34
  • @AndrewKepert Back to the drawing board Jul 13, 2016 at 14:50
16

I think that something like Asymptote is likely to be more conducive to this than anything TikZ-based, although tikz-3dplot can be helpful for faking 3D in simple cases.

Here's a very unskilled example based on Charles Staats's tutorial.

\documentclass{article}
\usepackage{asymptote}
\begin{document}
\begin{figure}
\centering
% Charles Staats: tutorial: page 78
\begin{asy}
  settings.outformat = "pdf";
  settings.render = 8;
  import three;
  currentprojection = perspective(4*(0,10,2),up=Y);
  size(4cm, 0);
  surface s = surface(reverse(scale(2)*unitcircle) ^^ unitcircle);
  draw(s, black, light=nolight);
\end{asy}
\end{figure}
\end{document}

To compile, use pdflatex (or engine of choice). Then run asy on the generated .asy. TeX Live includes asy although mine doesn't work, so I used a version packaged by my Linux distribution. Then run pdflatex again. (Obviously, this can be automated with the tool of your choice and adapted for your particular system.)

Here's the result.

Asymptote

This may not look terribly impressive, but the point is in the power and flexibility of Asymptote to configure and combine a range of different transformations and projections. For example, an orthographic projection may prove more useful than the perspective version above.

The crucial point is that Asymptote, unlike TikZ, knows about 3D. It deals in 3D objects rather than your having to fake them in 2D and this makes it easy to change perspective etc. It's disadvantage for me is that I am much less familiar with it than TikZ!

Here are some elected standard 2D transformations in TikZ:

\documentclass[tikz,border=10pt,multi]{standalone}
\begin{document}
\begin{tikzpicture}
  \draw [line width=1mm] circle (2.5mm);
  \begin{scope}[yscale=1.5, xshift=7mm]
    \draw [line width=1mm] circle (2.5mm);
  \end{scope}
  \begin{scope}[cm={1,0,-1,2,(1,1)}, xshift=10mm]
    \draw [line width=1mm] circle (2.5mm);
  \end{scope}
  \begin{scope}[transform canvas={yscale=3, rotate=3}, xshift=15mm]
    \draw [line width=1mm] circle (2.5mm);
  \end{scope}
\end{tikzpicture}
\end{document}

transformations

See tikz-3dplot for further possibilities.

4
  • 2
    These transformations are isometric at best, but certainly not in perspective. Where is the vanishing point on your ellipses? Jul 13, 2016 at 10:22
  • @StevenB.Segletes I think, as I say, that tikz-3dplot is an option here. Or something not TikZ for genuine 3D, of course.
    – cfr
    Jul 13, 2016 at 12:24
  • @StevenB.Segletes Actually, I think asymptote or similar is probably better for this if the OP wants genuine 3D flexibility. See edit above. (It doesn't look very impressive because I'm not at all familiar with Asymptote, but I wanted to indicate how it might be used here.)
    – cfr
    Jul 13, 2016 at 15:38
  • I agree that a non-latex solution, geared for such things, is infinitely better than what can be accomplished "within the box". Jul 13, 2016 at 15:42
9

The non-linear transformation stuff is a little bit slow and cannot be used with text, but here is a proof-of-concept idea. Two "source" corners of the original coordinate system along with then the four "target" corners before any non linear drawing drawing is done but after any transformations for the picture or scope. The source corners are always the south west and north west corners (in that order) of an un-transformed rectangle; the drawing should be within these corners. The four target corners are always specified anti-clockwise from south west.

I have no idea how the code will behave with "aggressive" transforms or rotations or slanting. Probably badly.

\documentclass[tikz,border=5]{standalone}
\usepgfmodule{nonlineartransformations}
\makeatletter
\def\tikz@scan@transform@one@point#1{%
  \tikz@scan@one@point\pgf@process#1%
  \pgf@pos@transform{\pgf@x}{\pgf@y}}
\tikzset{%
  grid source opposite corners/.code args={#1and#2}{%
   \pgfextract@process\tikz@transform@source@southwest{%
     \tikz@scan@transform@one@point{#1}}%
   \pgfextract@process\tikz@transform@source@northeast{%
     \tikz@scan@transform@one@point{#2}}%
  },
  grid target corners/.code args={#1--#2--#3--#4}{%
   \pgfextract@process\tikz@transform@target@southwest{%
     \tikz@scan@transform@one@point{#1}}%
   \pgfextract@process\tikz@transform@target@southeast{%
     \tikz@scan@transform@one@point{#2}}%
   \pgfextract@process\tikz@transform@target@northeast{%
     \tikz@scan@transform@one@point{#3}}%
   \pgfextract@process\tikz@transform@target@northwest{%
     \tikz@scan@transform@one@point{#4}}%
  }
}

\def\tikzgridtransform{%
  \pgfextract@process\tikz@current@point{}%
  \pgf@process{%
    \pgfpointdiff{\tikz@transform@source@southwest}%
      {\tikz@transform@source@northeast}%
  }%
  \pgf@xc=\pgf@x\pgf@yc=\pgf@y%
  \pgf@process{%
    \pgfpointdiff{\tikz@transform@source@southwest}{\tikz@current@point}%
  }%
  \pgfmathparse{\pgf@x/\pgf@xc}\let\tikz@tx=\pgfmathresult%
  \pgfmathparse{\pgf@y/\pgf@yc}\let\tikz@ty=\pgfmathresult%
  %
  \pgfpointlineattime{\tikz@ty}{%
    \pgfpointlineattime{\tikz@tx}{\tikz@transform@target@southwest}%
      {\tikz@transform@target@southeast}}{%
    \pgfpointlineattime{\tikz@tx}{\tikz@transform@target@northwest}%
      {\tikz@transform@target@northeast}}%
}


\begin{document}
\begin{tikzpicture}
\draw (0,0) grid (6,6);
\fill [even odd rule, opacity=0.5] 
  (3,3) circle [radius=2] circle [radius=1];
\begin{scope}[shift=(0:8),
  grid source opposite corners={(0,0) and (6,6)},
  grid target corners={(1,1) -- (3,2) -- (7,6) -- (-1,6)}]
\pgftransformnonlinear\tikzgridtransform
\draw [red] (0,0) grid (6,6);
\fill [red, even odd rule, opacity=0.5] 
  (3,3) circle [radius=2] circle [radius=1];
\end{scope}
\begin{scope}[shift=(270:8),
  grid source opposite corners={(0,0) and (6,6)},
  grid target corners={(1,1) -- (6,0) -- (5,4) -- (1,6)}]
\pgftransformnonlinear\tikzgridtransform
\draw [green] (0,0) grid (6,6);
\fill [green, even odd rule, opacity=0.5] 
  (3,3) circle [radius=2] circle [radius=1];
\end{scope}
\begin{scope}[shift={(8,-8)},
  grid source opposite corners={(0,0) and (6,6)},
  grid target corners={(1,1) -- (7,0) -- (5,8) -- (3,8)}]
\pgftransformnonlinear\tikzgridtransform
\draw [blue] (0,0) grid (6,6);
\fill [blue, even odd rule, opacity=0.5] 
  (3,3) circle [radius=2] circle [radius=1];
\end{scope}
\end{tikzpicture}
\end{document}

enter image description here

1
  • Actually this not quite what the OP requires, but it's a start. Jul 13, 2016 at 17:58
2

Same circle from different perspectives.

\documentclass{article}
\usepackage{tikz}
\usepackage{tikz-3dplot}

\begin{document}

\tdplotsetmaincoords{60}{110}
%
\pgfmathsetmacro{\rvec}{.8}
\pgfmathsetmacro{\thetavec}{30}
\pgfmathsetmacro{\phivec}{60}
%
\begin{tikzpicture}[scale=5,tdplot_main_coords]
\tdplotsetcoord{P}{\rvec}{\thetavec}{\phivec}
\tdplotdrawarc[red,line width=3mm]{(0,0,0)}{0.5}{0}{360}{}{}
\tdplotsetthetaplanecoords{\phivec}
\tdplotdrawarc[tdplot_rotated_coords,green,line width=3mm]{(0,0,0)}{0.5}{0}%
{360}{}{}
\tdplotsetthetaplanecoords{\thetavec}
\tdplotdrawarc[tdplot_rotated_coords,blue,line width=3mm]{(0,0,0)}{0.5}{0}%
{360}{}{}
\end{tikzpicture}
\end{document}

enter image description here

You must log in to answer this question.

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