How can I create this type of effect in tikz, random ink spots from a fountain pen, for a watermark on select feature pages.
-
1Do you need the light light/shadow effect it has? Do you already tried something? TikZ has various shadows and shadings.– QrrbrbirlbelJun 27, 2013 at 15:05
-
No I dont need the light shadow, it can be 'flat', however the non-symmetrical blotches are of interest.– Nicholas HamiltonJun 27, 2013 at 17:02
-
A bit offtopic: but one of my favorites is the coffee4 package (replaces ink by coffee) [texblog.org/tag/coffee4/]– Stefan WaldmannJun 28, 2013 at 12:57
-
Peripherally related: tex.stackexchange.com/questions/122970/…– Steven B. SegletesOct 1, 2018 at 17:11
2 Answers
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}
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
-
2The problem with the
rnd
circles becoming ellipses is due to the fact that circle internally sets bothx radius
andy radius
to the same value,rnd
in this case, which gets evaluated separately and therefore leads to two different radii. Same problem as in tex.stackexchange.com/a/119209/2552– JakeJun 27, 2013 at 16:46 -
Could replicate the shading from the OP image? That would be very nice.– dustinJun 28, 2013 at 0:36
-
1+1: nice!! I think you can also try to use radial shadings to get better effects: perhaps How to create a ball shading and to customize 3D lighting manually? can help. Jun 28, 2013 at 12:03
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}
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}
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}
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.