Artful Computing

Having been invited by Val Jordan - a local Stroud artist - to apply some of my transformation techniques to photographs of her large-scale works, it soon became apparent that sequences of images would be interesting - and once we began to look at sequences involving a few images one immediately thinks of using the computer to automatically produce hundreds and stitch them into a video. We displayed some of the results in the annual Stroud Select Trail event in 2017.

The Complex Mapping process creates some rather extreme transformations. In order to create a more extended video sequence I wondered whether it would now be worth breaking a couple of these extreme sequences with less dramatic clips, and the subject of the original print (the view of a lake from above) suggested using something based on refraction at a water surface. 

A Transformation of Val Jordan's Miami Lake
A Transformation of Val Jordan's Miami Lake

Hence, I developed the "Raindrops" transformation, which is specifically intended to be used to produce sequences of images representing spreading circular waves. (You will have to see YouTube or Val Jordan's website for a dynamic example, though. A static frame from the video is shown on the left.) Here, we are going to assume that our original source image is laid down at the bottom of a lake.

The calculation of the transformation is straightforward. The form of the wave is defined by $$h=\Sigma \frac{a_n}{r} \sin( k_n r - \omega_n t ) .$$

where \(r\) is radius from the point at which the raindrop is presumed to have fallen, \(k_n\) is the wave number of the \(n^{th}\) wave harmonic (that is, if \(\lambda_n\) is the the distance between wave peaks - the wavelength - \(k_n=1/\lambda_n\) which all means that the wave phase rotates through \(k_n\) radians for each unit in distance travelled), and \(\omega_n\) is the angular frequency (that is wave phase rotates through \(\omega\) radians each second). The speed at which a wave component travels is just \(v_n = \omega_n/k_n\).

In general, a typical wave train on the surface of water consists of a number of frequency components. We will want to add a little of this complexity to get a realistic type of wave - but since we are not doing real physics, we do not need to have lots of component, which would slow down the calculation. The speed at which a wave travels, and hence the right relation between \(k_n\) and \(\omega_n\), depends on the water depth. In "deep" water (i.e. depth larger than the wavelength) then wave speed is proportional to \(\sqrt \lambda\). In shallow water (depth smaller than wavelength) all waves travel at the same speed (which is proportional to the square root of water depth, d). Complicated stuff happens for waves of about the same wavelength as the water depth.

We can assume shallow water. In this case the slope of the wave surface \(\theta\) will be defined by:  

$$ \tan(\theta) = dh/dr=\Sigma k_n \frac{a_n}{r} \cos (k_n r - \omega_n t ) -\frac{a_n}{r^2} \sin (k_n r - \omega_n t ).$$

This is elementary calculus and trigonometry. This is all "elementary" is the sense of "this is where calculus and wave theory start" but actually needing A-level maths and physics, so don't worry if you don't follow it. I am documenting the maths to make the program easier to modify for those who do follow the reasoning.

In fact, we need to modify this formula, because of a potential division by 0 when \(r=0\) (and also a vertical slope). One simple (but unphysical solution) is to identify a minimum radius \(r_{min}\) below which the gradient of the wave surface is continued to r=0 at the same value it had at \(r=r_{min}\). A more sophisticated approach is using a quadratic for \(r <r_{min}\) matched to the same height and gradient at \(r=r_{min}\). We will use:

$$h = p + q.r^2 \implies dh/dr = 2.q.r$$

where p and q are constants that depend on time. We will need to solve for these. It is fairly easy to show that if \(h_{rmin}\) is the wave height when \(r=r_{min}\) as obtained from one of the equations above, then

$$ q = \frac{1}{2} \left( \frac{k_n}{r_{min}^2} - \frac{h_{min}}{r_{min}^2} \right) $$

which also then leads to 

$$p=h_{rain} - q.r_{min}^2$$

If we imaging a small boat that always stays aligned with the water surface, then its mast will be perpendicular to the water surface and it will depart from the vertical by an angle which is also \(\theta\). Now think of the mast as a pointer from the sky to the bottom of the lake. The line will touch the bottom not right below the boat but at a point displaced further out from the centre of the wave by an amount \(d .tan(\theta)\), which is conveniently just given by the equation for \(dh/dr\) above, multiplied by d.

For a set of concentric waves moving out from a point, for each point above the bottom of the lake, we can now add a little bit to the radius vector to get a point on the lake bottom. We might want to superimpose a number of raindrops, so we have to calculate the contribution of each wave train, as this displacement vector, and then add up all the vectors in the standard way (A-level maths again) to get the net displacement along the lake bottom at each point on the surface. 

Real physics means we should use Snell's Law of refraction, which says that the refractive index \(\eta\) is related to the angle of a light ray to the water surface above and below the water by: $$\eta = \frac{\sin(\theta_{above})}{\sin(\theta_{below})}.$$

This means that the point on the lake bottom which we would see through the distorted surface is not quite so far out as we would get by projecting our boat mast down to the bottom. For the sake of image transformation, however, we can save a bit of time-consuming maths by imagining that our wave heights are a little bit higher than the value in the equations above. It is not quite right: Snell's Law means that our approximation is pretty good near the wave peaks and troughs, but slightly overestimates the displacements in between. We will accept that because we are not trying to do real physics.

There is not really a lot of point in sticking exactly to real physics, because I am also going to introduce a non-physical twist in the displacements (purely for the interesting visual effect). That is, I will sometimes introduce a bit of displacement at right angles to the radial displacement. This is completely arbitrary and the technique is obvious from the code.

(An early version of this program is available here. A more sophisticated version of the program will be posted here in due course, but is in the process of being rationalised, documented and tidied up for publication.)

We would normally run this Processing program with the dynamic and autosave global variables set to true, so that it cycles (until stopped) incrementing time at each cycle to evolve the wave shape and saving a frame at each cycle. This generates rather a large number of separate image frames which then need to be stitched together using ffmpeg. (See my Video from Processing page.)

Breadcrumbs