68

Okay so I think everyone knows how to draw a sphere in Tikz

\tikz{
\fill [black] (-1,-1) rectangle (1,1);
\shade [ball color=white] (0,0) circle [radius=1cm];
}

But how can I add to it this effect that can be found in Penrose's drawings? (those dots around)

enter image description here

And in general how can one create that effect in Tikz for any shape? (Here are a couple of other images for your viewing pleasure)

enter image description here

enter image description here

enter image description here

enter image description here

  • I used to create a similar effect on a B&W graphics device by adding gray levels for pixels in a scan until it hit one, output a black pixel and subtract one from the running total. I seem to recall having to add a little noise to make the dots less regular. – John Kormylo Sep 10 '15 at 14:26
  • 1
    This would be fairly easy in lualatex, but in just latex+tikz this would be beyond my ability. – JPi Sep 10 '15 at 20:06
  • 5
    @JPi: It's perfectly acceptable (and encouraged) to post an answer that uses a package or engine other than the one that is mentioned in the question. I for one would love to see an approach that uses lualatex! – Jake Sep 10 '15 at 20:37
  • @Jake: ok, done. – JPi Sep 11 '15 at 1:32
72

This takes ages. Also some care is needed with the direction of the path and best effects are achieved by selectively applying the stippling to parts of the path. So, a real pain, basically. But (as with many things) it looks quite good from a distance.

\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{decorations}
\pgfkeys{/pgf/decoration/.cd,
  stipple density/.store in=\pgfstippledensity,
  stipple density=.1,
  stipple scaling function/.store in=\pgfstipplescalingfunction,
  stipple scaling function=sin(\pgfstipplex*180)*0.875+0.125,
  stipple radius/.store in=\pgfstippleradius,
  stipple radius=0.25pt
}
\pgfdeclaredecoration{stipple}{draw}{
\state{draw}[width=\pgfdecorationsegmentlength]{%
  \pgfmathparse{\pgfdecoratedcompleteddistance/\pgfdecoratedpathlength}%
  \let\pgfstipplex=\pgfmathresult%
  \pgfmathparse{int(\pgfstippledensity*100)}%
  \let\pgfstipplen=\pgfmathresult%
  \pgfmathloop%
  \ifnum\pgfmathcounter<\pgfmathresult\relax%
    \pgfpathcircle{%
      \pgfpoint{(rnd)*\pgfdecorationsegmentlength}%
        {(\pgfstipplescalingfunction)*(rnd^4)*\pgfdecorationsegmentamplitude+\pgfstippleradius}}% 
    {\pgfstippleradius}%
  \repeatpgfmathloop%
}
}

\tikzset{stipple/.style={
  decoration={stipple, segment length=2pt, #1},
  decorate,
  fill
}}
\begin{document}
\begin{tikzpicture}
\draw [postaction={stipple={amplitude=0.125cm}}] 
  (0,0) [rotate=45]  circle [radius=1];
\path [postaction={stipple={amplitude=0.25cm, stipple density=.35}},
  postaction={stipple={amplitude=0.35cm, stipple density=.15}}]
  (135:1) arc (135:315:1);
\end{tikzpicture}

\end{document}

enter image description here

And this takes even longer:

\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{decorations}
\pgfkeys{/pgf/decoration/.cd,
  stipple density/.store in=\pgfstippledensity,
  stipple density=(sin(\pgfstipplex*180)*0.25+0.1),
  stipple amplitude/.store in=\pgfstippleamplitude,
  stipple amplitude=(rnd^3)*\pgfstippley*(sin(\pgfstipplex*180)^2+0.05),
  stipple radius/.store in=\pgfstippleradius,
  stipple radius=0.25pt
}
\pgfdeclaredecoration{stipple}{draw}{
\state{draw}[width=\pgfdecorationsegmentlength]{%
  \pgfmathparse{\pgfdecoratedcompleteddistance/\pgfdecoratedpathlength}%
  \let\pgfstipplex=\pgfmathresult%
  \let\pgfstippley=\pgfdecorationsegmentamplitude%
  \pgfmathparse{int(abs((\pgfstippledensity)*100))}%
  \let\pgfstipplen=\pgfmathresult%
  \pgfmathloop%
  \ifnum\pgfmathcounter<\pgfmathresult\relax%
    \pgfpathcircle{%
      \pgfpoint{(rnd-0.5)*\pgfdecorationsegmentlength}%
        {(\pgfstippleamplitude)+\pgfstippleradius}}% 
    {\pgfstippleradius}%
  \repeatpgfmathloop%
}
}

\tikzset{stipple/.style={
  decoration={stipple, segment length=2pt, #1},
  decorate,
  fill
}}
\begin{document}
\begin{tikzpicture}[x=2em,y=2em]
\draw [postaction={stipple={amplitude=0.5cm}},
      postaction={stipple={amplitude=0.25cm}}] 
  (2,1) ..controls ++(135:1) and ++(90:1) .. 
  (0,0) .. controls ++(270:1) and ++(180:1.5) .. 
  (2,-2) .. controls ++(0:1.5) and ++(270:2) .. 
  (5,1) .. controls ++(90:2) and ++(75:1) .. 
  (2,1) .. controls ++(255:1/4) and ++(0:1/2) .. (3/2,0);
\draw [postaction={stipple={amplitude=0.125cm, stipple density=0.05}},
  postaction={stipple={amplitude=0.5cm, reverse path}},
  postaction={stipple={amplitude=0.25cm, reverse path}}] 
  (3,0) .. controls ++(135:1) and ++(90:1) .. (4,1);
\end{tikzpicture}
\end{document}

enter image description here

  • 2
    Wow, that looks amazing! – Jake Sep 11 '15 at 8:40
  • 5
    That looks f*** awesome. I didn't expect something so close. – MyUserIsThis Sep 11 '15 at 8:43
  • 4
    Spectacular! I'm already using it! – Gonzalo Medina Sep 11 '15 at 17:11
  • 1
    Why did you put a % after \pgfmathresult and \pgfdecorationsegmentamplitude? Are those macros "special" in some way? – Pier Paolo Sep 11 '15 at 18:34
  • 2
    @PierPaolo no, they aren't special, actually I usually put % at the end of most lines involving pgf basic layer stuff (even when it is technically not necessary). I was just "lazy" in this case. – Mark Wibrow Sep 11 '15 at 19:44
36

Looks like I’ll have to step up my game here. Mark Wibrow added a great answer, but this one has a few points in its favor, including grayscaling the dots so the ones on the inside are lighter, getting exactly predictable and replicable results on each run, and having control over all the parameters. (For example, you can change how much the thickness of the stippling varies by changing the exponent of \thisrowno{1} in both places.) It also runs pretty quick while working in engines other than LuaLaTeX.

\PassOptionsToPackage{svgnames}{xcolor}
\documentclass{standalone}
\usepackage{pgfplots}

\pgfplotsset{width=\textwidth,compat=1.12}

\begin{document}

\begin{tikzpicture}
\begin{axis}[trig format plots=rad, width=5cm,axis equal,
             xmin = -1, xmax = 1,
             axis x line = none, axis y line = none]
  \addplot [variable=\t,domain=0:2*pi,samples=30,smooth]({cos t},{sin t});
%% θ(u) = 2πku, r(t) = t^c
  \addplot+ [scatter,scatter src=0.6*(1-\thisrowno{0}^0.125)+0.2,
             only marks,mark=*,mark size=0.001cm,colormap/blackwhite]
            table[header=false,
                  x expr=\thisrowno{0}^0.125*cos(7*pi/3+sign(\thisrowno{2}-0.5)*pi*\thisrowno{1}^0.5),
                  y expr=\thisrowno{0}^0.125*sin(7*pi/3+sign(\thisrowno{2}-0.5)*pi*\thisrowno{1}^0.5),
                 ]
                 {randtuple.dat}; % A file of three columns of random numbers from [0,1).
\end{axis}
\end{tikzpicture}

\end{document}

stippling of varying thickness small stippling of varying thickness large

For completeness, here’s the program that generated the random data, although it would be possible to generate it within pgfmath using rand:

#include <cmath>
#include <ctime>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <random>

using std::cout;

int main(void)
{
  static const ssize_t ncols = 1000;
  static const unsigned sigfigs = 17;
  static const unsigned width = 22;
  static const double interval = std::exp2(-64.0L);

  std::mt19937_64 rng( static_cast<std::mt19937_64::result_type>(
                         time(NULL)*CLOCKS_PER_SEC+clock() ) );

  cout.precision(sigfigs);

  for ( ssize_t i = 0; i < ncols; ++i ) {
    const double x = rng() * interval;
    const double y = rng() * interval;
    const double z = rng() * interval;

    cout << std::setw(width) << x << " "
         << std::setw(width) << y << " "
         << std::setw(width) << z << "\n";
  }

  return EXIT_SUCCESS;
}

We might also take columns of points (t,u) ∊ [0,1]×[0,1] drawn from a uniform random distribution, and map them to r(t) = t^c, θ(u) = 2πu.

And here’s what that looks like:

enter image description here

\PassOptionsToPackage{svgnames}{xcolor}
\documentclass{standalone}
\usepackage{pgfplots}

\pgfplotsset{width=\textwidth,compat=1.12}

\begin{document}

\begin{tikzpicture}
\begin{axis}[trig format plots=rad, width=5cm,axis equal,
             xmin = -1, xmax = 1,
             axis x line = none, axis y line = none]
  \addplot [variable=\t,domain=0:2*pi,samples=30,smooth]({cos t},{sin t});
%% θ(u) = 2πku, r(t) = t^c
  \addplot+ [scatter,scatter src=0.8*(1-\thisrowno{0}^0.125),
             only marks,mark=*,mark size=0.001cm,colormap/blackwhite]
            table[header=false,
                  x expr=\thisrowno{0}^0.125*cos(10*pi*\thisrowno{1}),
                  y expr=\thisrowno{0}^0.125*sin(10*pi*\thisrowno{1}),
                 ]
                 {randpairs.dat}; % A file of two columns of random numbers from [0,1).
\end{axis}
\end{tikzpicture}

\end{document}

And the old version

Maybe get a noisy distribution of points on a line, paramaterize those as a spiral that coils more tightly on the outside then the inside, e.g. for t ∊ [0,1], k>1, 0<c<1, θ(t) = 2πkt, r(t) = t^c, and plot them?

\PassOptionsToPackage{svgnames}{xcolor}
\documentclass{standalone}
\usepackage{pgfplots}

\pgfplotsset{width=\textwidth,compat=1.12}

\begin{document}

\begin{tikzpicture}
\begin{axis}[trig format plots=rad, width=5cm,axis equal,
             xmin = -1, xmax = 1,
             axis x line = none, axis y line = none]
  \addplot[variable=\t,domain=0:2*pi,samples=30,smooth]({cos t},{sin t});
%% θ(t) = 2πk·√t, r(t) = t^c
%% x(t) = r(t) cos θ(t) = t^c cos 2πk√t
%% y(t) = r(t) sin θ(t) = t^c sin 2πk√t
%% Where t ∊ [0,1], k>1, 0<c<1
%%
%% To eliminate the first half-revolution, solve for 2πk√t = π.  A prime
%% number in the sample size is less likely to produce unattractive patterns.
  \addplot+[variable=\t,domain=0.01:1,samples=193,
            only marks,mark=*,mark size=0.01cm,
            mark options={draw=DimGray,fill=DimGray}]
           ({t^0.125*cos(10*pi*t^0.5)},{t^0.125*sin(10*pi*t^0.5});
\end{axis}
\end{tikzpicture}

\end{document}

enter image description here

For simplicity, there’s no noise in the distribution of the points, but it still looks fairly nice to me.

  • Once I start doing grayscale, though, I guess I might as well just run a shading algorithm on each coordinate. But that might look too realistic here! – Davislor Sep 11 '15 at 20:39
  • +1 for using std::random, perhaps this (determinisitic) sequence can achieve more uniform covering by sacrificing a bit of randomness people.sc.fsu.edu/~jburkardt/cpp_src/sobol/sobol.html – alfC Sep 12 '15 at 19:12
  • Your C++ code is not so nice. I recommend using std::uniform_real_distribution. Also there is not reason for those constants to be static. Here is my approach: hastebin.com/raw/usojakivew – Henri Menke Apr 9 '18 at 21:26
  • @HenriMenke If I were writing that code today, I would do it differently. For example, those static const variables would be constexpr. All variables local to main() are now static by default, but that was not the case when I learned C and C++. Many older compilers allowed you to call main(), and by default would have allocated those variables on the stack without staticand perhaps not folded the constants. – Davislor Apr 9 '18 at 21:46
  • 1
    @Davislor Please add the syntax highlighting again after rolling back. That, I believe, is correct ;-) – Henri Menke Apr 9 '18 at 22:54
23

Ok, here goes, compile with lualatex.

\documentclass{article}

\usepackage{tikz}
\usepackage{luacode}



\begin{document}


\begin{tikzpicture}
\draw (0,0) circle(5cm);
\begin{luacode*}
math.randomseed(os.time())
for i=1,1000 do
r=math.random()*3.145926535*2
s=math.random()+3.9
tex.print("\\draw[fill] (" .. s*math.cos(r) .. "," .. s*math.sin(r) ..") circle(0.2mm);")
end
 \end{luacode*}
\end{tikzpicture}

\end{document}

enter image description here

And a somewhat more sophisticated example:

\documentclass{article}

\usepackage{tikz}
\usepackage{luacode}



\begin{document}


\begin{tikzpicture}
\draw (0,0) circle(5cm);
\begin{luacode*}
math.randomseed(os.time())
for i=1,1000 do
r=math.sqrt(math.random())*3.145926535*2
s=math.pow(math.random(),0.2)+3.99
tex.print("\\draw[fill] (" .. s*math.cos(r) .. "," .. s*math.sin(r) ..") circle(0.2mm);")
end
 \end{luacode*}
\end{tikzpicture}

\end{document}

enter image description here

Better yet:

\documentclass{article}

\usepackage{tikz}
\usepackage{luacode}



\begin{document}


\begin{tikzpicture}
\draw (0,0) circle(5cm);
\begin{luacode*}
math.randomseed(os.time())
for i=1,1000 do
r=math.sqrt(math.random())*3.145926535*2
s=math.pow(math.random(),0.1)*4.99
tex.print("\\draw[fill] (" .. s*math.cos(r) .. "," .. s*math.sin(r) ..") circle(0.2mm);")
end
 \end{luacode*}
\end{tikzpicture}

\end{document}

enter image description here

Best:

\documentclass{article}

\usepackage{tikz}
\usepackage{luacode}



\begin{document}


\begin{tikzpicture}
\draw (0,0) circle(5cm);
\begin{luacode*}
math.randomseed(os.time())
for i=1,1000 do
if math.random() > 0.5 then b=1 else b= -1 end
r=(b*math.sqrt(math.random())+1)*math.pi
s=math.pow(math.random(),0.1)*4.99
tex.print("\\draw[fill] (" .. s*math.cos(r) .. "," .. s*math.sin(r) ..") circle(0.3mm);")
end
 \end{luacode*}
\end{tikzpicture}

\end{document}

enter image description here

  • 1
    This was fun.... – JPi Sep 11 '15 at 1:39
  • Lua’s random number generator seems to favor higher numbers over lower ones. – Davislor Sep 11 '15 at 5:36
  • I'm drawing random numbers from a power distribution instead of a uniform. – JPi Sep 11 '15 at 8:34
  • And have indicated a range. – JPi Sep 11 '15 at 8:35
  • It’s introducing a visible discontinuity around θ=0. If we want the dots to be clustered around one side, maybe raised-cosine distribution? – Davislor Sep 11 '15 at 8:40
17

I guess this is not exactly what you want. But it looks like pictures from old books as well.

I just applied ordered dithering to the sphere here.

Theoretically, given any functional shading, it is always possible to apply a dithering by a post script. (unless the stack overflows.)

The resolution is hard-coded, but still it is possible to change the resolution. (But it is meaningless if the resolution is too high.)

\documentclass[tikz,border=9]{standalone}

\pgfdeclarefunctionalshading{ordered dithering sphere}{\pgfpoint{0bp}{0bp}}{\pgfpoint{50bp}{50bp}}{}{
    2 copy
    %
    50 div 128 mul floor 63.5 sub 64 div exch
    50 div 128 mul floor 63.5 sub 64 div exch
    2 copy
    dup mul exch dup mul add sqrt 3 1 roll
    %
    2 copy 
    dup mul exch
    dup mul add
    1.0 sub
    0.3 dup mul
    -0.5 dup mul add
    1.0 sub
    mul abs sqrt
    exch 0.3 mul add
    exch -0.5 mul add
    dup abs add 2.0 div 
    0.6 mul 0.4 add
    %
    exch .98 ge {pop 1} if
    3 1 roll
    %
    50 div exch 50 div 1 % y x 1
    %
    3 1 roll 8 mul dup floor sub
    2 1 roll 8 mul dup floor sub
    3 2 roll
    %
    3 1 roll 2 mul dup floor dup 3 1 roll sub 3 2 roll 2 mul dup floor dup 3 1 roll sub
    4 3 roll 3 2 roll 2 copy -4 mul mul exch 3 mul add exch 2 mul add 4 mul 4 3 roll add
    %
    3 1 roll 2 mul dup floor dup 3 1 roll sub 3 2 roll 2 mul dup floor dup 3 1 roll sub
    4 3 roll 3 2 roll 2 copy -4 mul mul exch 3 mul add exch 2 mul add 16 mul 4 3 roll add
    %
    3 1 roll 2 mul dup floor dup 3 1 roll sub 3 2 roll 2 mul dup floor dup 3 1 roll sub
    4 3 roll 3 2 roll 2 copy -4 mul mul exch 3 mul add exch 2 mul add 64 mul 4 3 roll add
    %
    3 1 roll 2 mul dup floor dup 3 1 roll sub 3 2 roll 2 mul dup floor dup 3 1 roll sub
    4 3 roll 3 2 roll 2 copy -4 mul mul exch 3 mul add exch 2 mul add 256 mul 4 3 roll add
    %
    1025 div
    3 index
    le
    {1}{0}ifelse
    dup dup
}

\begin{document}
    \tikz\shade[shading=ordered dithering sphere](0,0)circle[radius=5cm];
\end{document}
  • that's wild.... – JPi Sep 11 '15 at 13:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.