Random ink blotches from tikz

See update.

\documentclass{article}
\usepackage{tikz}
\begin{document}
\usetikzlibrary{calc}

\def\blob#1#2{\draw[fill,rounded corners=#1*3mm] (#2) +($(0:#1*2+#1*rnd)$)
\foreach \a in {20,40,...,350} {  -- +($(\a: #1*2+#1*rnd)$) } -- cycle;}

\begin{tikzpicture}
\blob{0.4}{0,0}
\foreach \a in {0,20,...,350} {
\fill[black] let \p1 = (\a+20*rnd:3*rnd),
                     \n1 = {0.2*rnd}
                  in (\p1) circle(\n1);
 }

\blob{0.2}{1,3}
\foreach \a in {0,20,...,350} {
\fill[black] let \p1 = ($(1,3)+(\a+20*rnd:2*rnd)$),
                     \n1 = {0.15*rnd}
                  in (\p1) circle(\n1);
 }
\end{tikzpicture}
\end{document}

Ink drops

Explanations

The picture is composed by two kind of "objects". The irregular shaped blotches, which are drawn by the macro \blob, and lots of smaller black circles.

The \blob macro takes two arguments. The first one is a scale factor. The second one is the coordinates of its center. The macro uses polar coordinates to generate a series of points at regular angles, but random distances from its center, and connects all of them with a rounded corners polyline, which is filled in black.

The smaller circles are drawn with a similar technique, using polar coordinates but this time not only the distance to the center, but also the angles are random. The center of each drop is calculated as the point \p1. The radius of each drop is also random, and precomputed in \n1.

For some unknown reason, if I tried to avoid the let...in syntax and use directly rnd in the expressions, such as circle(0.2*rnd), for example, I got some non-circular drops (very eccentric ellipses), so I resorted to the let...in syntax to precompute the center and radii, and the problem disappeared.

Update

As requested in a comment, I added shadows and shines. It was more difficult than expected, because, since all the shapes and locations are inherently random, but the shadows and shines had to be drawn at the same places and with the same shape than the random drops, I had to store the points of the contour to draw it twice. Also, since some drops can be drawn one "on top" of the other (because of its random positioning), this could cause the shadow of a drop to be drawn on top of the ink of another drop, which is ugly. I had to add pgflayers to ensure that all shadows are in the background.

I tried to devise a better algorithm to decide the size of the random drops, basend on their distance to the center of the splat. I'm not entirely satisfied with the result, but it is not too bad.

Code

\documentclass{article}
\usepackage{tikz}
\begin{document}
\usetikzlibrary{calc}

% Initial setup
\pgfmathsetseed{1234}
\pgfdeclarelayer{shadows}
\pgfdeclarelayer{ink}
\pgfdeclarelayer{lights}
\pgfsetlayers{shadows,main,ink,lights}

% Macro to draw the big random shaped  blobs
\def\blob#1#2#3{ % center, Max radius, min radius,
% Compute the points of the path
\foreach \a in {0,20,...,350} {
  \path let
    \p1 = (#1),             % center
    \n1 = {#2+(#3-#2)*rnd}  % distance at center
    in (\p1) node[coordinate] (P\a) at +(\a:\n1) {};
}
% Draw first the shadow (-.5mm, -1mm) is the offset
\begin{pgfonlayer}{shadows}
\fill let \n1 = {(#3-#2)*10/6} in
   [fill=black!20,,rounded corners=\n1 mm] ($(P0)+(-.5mm,-1mm)$)
   \foreach \a in {20,40,...,350} {  -- ($(P\a)+(-.5mm,-1mm)$) } -- cycle;
\end{pgfonlayer}
% Then the ink
\begin{pgfonlayer}{ink}
\draw let \n1 = {(#3-#2)*10/6} in
   [fill,rounded corners=\n1 mm] (P0)
   \foreach \a in {20,40,...,350} {  -- (P\a) } -- cycle;
\end{pgfonlayer}
% I tried to draw a light near to the upper border, using the same path, but
% the results were bad, and I deleted it
}

% Macro to draw a drop of ink, relative to one splat
\def\drop#1#2#3#4{ % center of splat, angle, min spread, max spread
 \pgfmathsetmacro{\distance}{#3+(#4-#3)*rnd}
 \pgfmathsetmacro{\distancetoborder}{abs(\distance-#3)}
 \pgfmathsetmacro{\size}{#3/(15+10*rnd)/sqrt(\distancetoborder)}
 \pgfmathsetmacro{\angle}{#2+20*rnd}
 \begin{pgfonlayer}{shadows}
 \fill[black!20] ($(#1)+(-\size/5,-\size/3)$) +(\angle:\distance) circle(\size);
 \end{pgfonlayer}
 \begin{pgfonlayer}{ink}
 \fill[black]    (#1) +(\angle:\distance) circle(\size);
 \end{pgfonlayer}
 \begin{pgfonlayer}{lights}
 \fill[white]    (#1) ++(\angle:\distance) ++(80:0.7*\size) circle(0.2*\size);
 \end{pgfonlayer}
}

% Main drawing
\begin{tikzpicture}
\blob{0,0}{1}{3}
\foreach \a in {0,10,...,350} {
  \drop{0,0}{\a}{2.5}{5}
}
\blob{2,4}{0.3}{1}
\foreach \a in {0,20,...,350} {
  \drop{2,4}{\a}{1}{3}
 }
\end{tikzpicture}
\end{document}

Result

With shadows


With PSTricks. Too long as a comment.

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-plot,pst-node}
\usepackage[nomessages]{fp}
\pstVerb{realtime srand}
\expandafter\FPseed\expandafter=\pdfuniformdeviate 1000000

\def\blotches#1{%
    \psLoop{#1}{%
        \def\points{}%
        \rput(!Rand 12 mul 3 sub Rand 12 mul 3 sub){%
            \FPrandom\NP
            \FPeval\NP{round(NP*17+3:0)}%
            \curvepnodes[plotpoints=\NP]{0}{360}{Rand t PtoC}{P}%
            \multido{\i=0+1}{\Pnodecount}{\xdef\points{\points(P\i)}}%
            \expandafter\psccurve\expandafter*\points}}%
    \ignorespaces}

\def\bubbles#1{%
    \psLoop{#1}{%
        \qdisk(!Rand 12 mul 3 sub Rand 12 mul 3 sub){!Rand 4 div}}%
    \ignorespaces}

\begin{document}
\begin{pspicture}(-3,-3)(3,3)
    \bubbles{100}
    \blotches{20}
\end{pspicture}
\end{document}

enter image description here

Animation

To ease choosing the most realistic output.

Ink

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-plot,pst-node}
\usepackage[nomessages]{fp}
\pstVerb{realtime srand}
\expandafter\FPseed\expandafter=\pdfuniformdeviate 1000000

\def\blotches#1{%
    \psLoop{#1}{%
        \def\points{}%
        \rput(!Rand 12 mul 3 sub Rand 12 mul 3 sub){%
            \FPrandom\NP
            \FPeval\NP{round(NP*13+7:0)}%
            \curvepnodes[plotpoints=\NP]{0}{360}{Rand 2 mul t PtoC}{P}%
            \multido{\i=0+1}{\Pnodecount}{\xdef\points{\points(P\i)}}%
            \expandafter\psccurve\expandafter*\points}}%
    \ignorespaces}

\def\bubbles#1{%
    \psLoop{#1}{%
        \qdisk(!Rand 12 mul 3 sub Rand 12 mul 3 sub){!Rand 4 div}}%
    \ignorespaces}

\begin{document}
\psLoop{10}{%
\begin{pspicture}(-3,-3)(3,3)
    \bubbles{200}
    \blotches{10}
\end{pspicture}}
\end{document}

enter image description here

Blood

Hopefully blood blotches are more interesting.

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-plot,pst-node}
\usepackage[nomessages]{fp}
\pstVerb{realtime srand}
\expandafter\FPseed\expandafter=\pdfuniformdeviate 1000000

\def\blotches#1{%
    \psLoop{#1}{%
        \def\points{}%
        \rput(!Rand 12 mul 3 sub Rand 12 mul 3 sub){%
            \FPrandom\NP
            \FPeval\NP{round(NP*13+7:0)}%
            \curvepnodes[plotpoints=\NP]{0}{360}{Rand 1.5 mul t PtoC}{P}%
            \multido{\i=0+1}{\Pnodecount}{\xdef\points{\points(P\i)}}%
            \expandafter\psccurve\expandafter*\points}}%
    \ignorespaces}

\def\bubbles#1{%
    \psLoop{#1}{%
        \qdisk(!Rand 12 mul 3 sub Rand 12 mul 3 sub){!Rand 4 div}}%
    \ignorespaces}

\psset{linecolor=red}
\begin{document}
\psLoop{10}{%
\begin{pspicture}(-3,-3)(3,3)
    \bubbles{250}
    \blotches{10}
\end{pspicture}}
\end{document}

enter image description here

Attention

Note that Rand no longer produces a random real number between 0 and 0.5 inclusive. Its definition had been tacitly changed. Now it produces a random real number between 0 and 1 inclusive. It is not documented, nor announced, but it is still fun!

The code given above has not been updated yet so it will produce different output. I have no time to update it right now. Sorry for this inconvenience.

Tags:

Tikz Pgf