-do … -while

Figure 1: Caution: Electrified 1911 Goudy Bookletter Ampersand. Please do not stand in any water puddles or other conductive liquids while following this tutorial. Thank you.

A -do … -while block repeatly executes until the condition given as an argument to -while becomes false. Execution then continues with the commands following -while.

The format of the command is:

-do <statements> …-while <condition>

The condition is an expression reducing to either a numeral or a string. So long as the numeral is non-zero or the string resolves to a existing file (boolean true), the commands, if any, enclosed in the -do … -while block execute repeatedly. Zero or a file that can no longer be found (boolean false) stops the iteration.

Commands enclosed within a -do … -while block always execute at least once. Following an initial pass through the block, the interpreter evaluates the condition expression for the first time and only if it is true, recommences execution at the first command following -do. Otherwise the interpreter proceeds to the next command following -while.

The commands enclosed in the -do … -while block never cease executing if the condition remains true. In anticipation of this possibility, a programmer can build an 'escape hatch' within the body of the -do … -while block, using the -break command, usually in the body of an -if-endif block. Similarly, a -continue command terminates the current iteration and forces an immediate re-evaluation of -while's conditional argument.

Usually, the commands constituting the body of the -do … -while block have some direct or indirect effect on the condition given as an argument to -while. Searching for items that meet certain criteria is a case where one uses a -do … -while block; eventually finding items that match the search criteria flips the condition given to -while, finishing the search loop.

Example: Rivers And Lightning, Oh My!

Two points which are distant "as the crow flies" may be effectively close at hand when connected by a smooth-surface, graded road. On the other hand, a point "nearby" in terms of flying crows is, as a practical matter, distant when there are large mountains or oceans, or implacibly fierce Corgis inhabiting the intervening regions. When factors other than spatial separation alter the effective distances between points, the -distance command can take a "cost map" as an argument, which account for such factors and which alters how the -distance command records the separation between points and isovalues.

This map assigns a cost to each pixel and when such a map is in place, the distance between any two points becomes the sum of costs of intervening points. This gives rise to a minimal path, that collection of intervening points which sum to the smallest cost, the effective "shortest distance" connecting two points. The minimal path need not be a straight line; it may even look like a river or a lightning bolt.

Potential Maps

The -distance command produces a potental or return map as an additional output when given a cost map and mode arguments of three or four (low- or high-connectivity Dijkstra metric functions, respectively. See -label for further remarks on connectivity). The potential map produces minimal paths connecting arbitrary points to isovalues — that particular value establishing reference points from which one measures distances.

For each pixel in the selected image, the potential map associates a routing directive, encoding the direction that some imagined cursor should go when following the minimal path from that pixel to the nearest isovalue. The routing directive consists of three, two bit fields, one each for width, height and depth directions. If set, the least significant bit of each field directs the cursor to move one pixel toward the origin, decrementing the x, y or z coordinate; conversely, setting the most significant bit moves the cursor away from the origin, incrementing the x, y or z coordinate. It is meaningless to set both bits, simultaneously steering the cursor away from and toward the origin, but both bits may be cleared, indicating no movement along that axis.

Commencing from an arbitrary pixel with a non-zero routing directive, the imagined cursor steps once to the neighboring pixel that may or may not be closer to the nearest isovalue "as the crow flies", but is on the cheapest path to that point, according to the -distance command with cost map that computed the routing directive. The cursor then assesses the routing directive of the neighboring pixel and if that too is non-zero, steps furher on, repeating the cycle until encountering a routing directives equal to zero. This permanently stops the imagined cursor, which has been given no further direction in which to move and can no longer obtain new routing directives, but the course it has traced is the minimal path from the initial pixel to the nearest isovalue. In a properly composed potential map, zero-valued routing directives correspond to isovalues in the selected image and all other pixels have non-zero routing directives so composed as to steer an imagined cursor toward the nearest isovalue.

Making Rivers, Perhaps, or Maybe Lightning Bolts

The lightning bold crackling fiercely in the frontpiece stems from some 180 arbitrarily chosen points, from which minimal paths were traced to a single isovalue. For the cost map, we rendered a mode 0 -turbulence pattern, further sharpened through inverse diffusion. We then set an isovalue of one in an otherwise black image at a location coincident to the minimal value of the turbulence image.

Astute readers will, no doubt, recognize that we are playing the Watershed Game, tracing the paths of so many droplets dribbling their ways downslope to the basin containing the minimum point. This behavior follows readily from the turbulence-generated cost map. This particular turbulence forms discontinuous rilles which tend to snake in river-like ways. They are also local minima, cheap pixels over which one may traverse when the turbulence is used as a cost map. Our game for river or lightning bolt emulation is straightforward: harness -distance to generate a potential map corresponding to a turbulence image. Then, for a supply of a few hundred arbitrarily chosen pixels, trace the minimal paths of each to the isovalue. If we render these paths in dark gray and then composite them through an additive operator, the more heavily traveled paths will tend to whites and light greys; less traveled paths will be darker. Particulars follow.

Height, Cost and Potential Maps

gmic -srand 9717 \
-input 1024,1024,1,1 \
-turbulence[-1] 512,12,3,0,0 \
The genesis of the frontpiece illustration is mode 0 -turbulence. We base the height, cost and potential map on this underlying rendering.
As noted in the -plasma tutorial, it is our wont to set a particular seed in G'MIC's random number generator (via -srand) so that we can reproduce a particular pattern; what we have here may as well be called "Turbulence Pattern 9717".
Mode 0 turbulence employs the -abs command as a mixer; at each stage of its development, any zero-crossings are folded back into the positive region, generating numerous discontinuities – creases – both large and small. These emulate rilles and waterways, somewhat. Being local minima, the rilles have low intensities; when we derive a cost map from this turbulence, rivers or lightning bolts will tend to follow these rilles. We revisit this idea downstream from here.
We render the turbulence so that it exhibits large-scale features, develop the pattern through 12 octaves and set a modest decay factor of three between each octave. While large-scale features are present, low-magnitude, small-scale features persist.
-input http://particularart.com/static/media/uploads/command_reference/repeat/ampersand_goudybl1911.svg \
-resize[-1] [-2],[-2],[-2],[-2],5,0 \
-blur[-1] 11,1,0 \
-normalize[-1] 0.4,1 \
-mul[-2,-1] \
-sharpen 50 \
--sqr[-1] \

From this point forward, we modify the turbulence patterns in various ways to make our height, cost and potential maps.
The first modification is impressing a Goudy 1911 Bookletter ampersand on the turbulence field, mainly because we are obsessed with this ampersand (but you knew that), and secondarily, we'd like to steer our lightning bolts so that they roughly trace parts of the ampersand's shape. Since we will trace the lightning bolts by the Watershed Game, we go about making a general depression in the turbulence in the shape of the ampersand; we blur the ampersand slightly to avoid abrupt drops into the basin.

We anticipate that as lightning bolts trace along minimal paths, those will thread through the lower elevation ampersand on the way to the isovalue. Once we have blurred and scaled the ampersand, we multiply it with the original turbulence, giving us the final form of our height map. Our cost map follows directly from sharpening and squaring operations. Sharpening tends to magnify fine detail, alternately, increasing or decreasing the values of out-of-bound pixels, driving them from the mean value of the image and towards its extremes. Squaring makes expensive (light colored) pixels even more expensive. In the larger scheme of things, we are making a cost map where minimal paths will meander a great deal, as we are creating pits and rises and other obstacles that mitigate against the straightforward progression of minimal paths to the isovalue.

-input 100%,100%,100%,100% \
-set[-1] 1,'{-3,c}' \
-distance[-1] 1,[-2],4 \
-output[-1] potmap.cimg,uchar \
-output[0] height.png \

 We harness the -distance command to make a potential map.

To that end, we require an image in which we can measure distances. We don't have one, but one is pretty easy to make. We conjure a black field from the aether and set one isovalue pixel within, this situated in the same location as the minimal point in our turbulence height map. It is to this point that all lightning bolts meander when we play the Watershed Game.

It is worthwhile to note the substitution sequence, {-3,c}, that furnished the location arguments to the -set command. It is one of a large class of substitution sequences which resolve to bits of statistical information about images on lists. 'c' requests the coordinates of the minimum point; '-3' selects the third image, counting from the end of the list. That is the current home of the height map on the image list. By using this substitution sequence, we can change the turbulence pattern on a whim and this substitution sequence would adjust automatically to the coordinates of the new minimal point. This and its many, many substitution sequence siblings occupy the bulk of section 1.8 of the G'MIC Handbook

We invoke the distance command to measure distances from every point in our conjured image to the single isovalue. Ah, but these aren't simple, "as the crows fly" distances of Euclidean spaces, but distances that reflect the traversal expenses from our turbulence-based cost map, developed in the previous panel. We pass the cost map as the second argument to the -distance command. The first argument should be familiar to most readers; it identifies the isovalue. Pixels with this value become the benchmarks in images from which distances are measured. We use 'one' here, the precise same value we used when plotting the location of the of the minimal point in our conjured image.

The last argument, '4', requests a high connectivity Dijkstra metric function. It behavior approaches that of the Euclidean metric when the values in the cost map approach the same value, which is not (much) the case in this example.  Our main interest in this metric is the potential map it also produces, because that is the map which underlies our Watershed Game.

We save out the first image on the list, the height map, depicted on the immediate left. Downstream, it is involved with -light_relief and other fake lighting methods, which many of you will think of as "bump mapping". Finally, we save out the potential map, taken care to specify to the -output command a particular data dype, unsigned character, to prevent the default conversion to floating point, a calamity which would destroy the routing directives stored in this map. Observe, too,  that we save the potential map in G'MIC's native CIMG format to further avoid the inadvertant casting of the routing directives into an unreadable format.

The conventional output from -distance is a displacement map which furnishes point-wise offsets of each pixel in the original image to the nearest isovalue. For the Great Watershed Game, we use the potential, or return map instead.

This data set is a point-wise collection of control words, described above, which we call routing directives, one corresponding to each pixel in the selected image. Given a pixel, the associated routing directive conveys to an imagined cursor the least expensive direction it needs to step in order to move "closer" to the isovalue, in quotes because the routing directive may shift the imagined cursor away from the isovalue, at least in terms of Euclidean distance. However, when -distance is furnished with a point-wise cost map, as we have done here, the "shortest distance" is the minimal path with the lowest cost sums, which likely is not the shortest distance by Euclidean measure.

On the left, we have schematically reproduced the 9×9 pixel neighborhood around the minimum point in the potential map. The numerals reprsent routing directives, each with three two-bit directional fields that collectively move an imagined cursor away from, or toward the origin, along each cardinal direction. Numerals 1, 5, 4, 6, 2, 10, 8 and 9 direct an imagined cursor, respectively, west, northwest, north, northeast, east, southeast, south or sourthwest. The special routing directive 0 tells the cursor not to move; once a cursor is given such a directive it necessarily stalls, as it has been told to go nowhere so can never acquire another directive.

The -distance command computes these point-wise routing directives based on the cost map; in aggregate, these routing directives put an imagined cursor at a given point on a minimal path toward the nearest isovalue, one that accumulates the least cost along its length.

This brings us to the Watershed Game. Both rivers and lightning bolts, among other phenomena, attempt to minimize a cost of travelling from an initial to a destination point. Armed with a potential map such as the one on the left, we can emulate droplets of water streaming from arbitrary locations towards some minimum point. Our working mechanism is a -do … -while block: while routing directives are non-zero, step in the direction it gives us, then read a new directive. Repeat the block. Continue doing so until we read a zero-valued routing directive. We have arrived at the minimum point and the path followed respresents the smallest cost possible from our starting point, whatever that may be. In doing this, of course, we trust that -distance has computed all of the routing directives correctly.

Particulars follow.

Walking a Minimal Path

gmic pcnt=180 -input potmap.cimg \
-input 100%,100%,100%,100% \
-input '{$pcnt}',2,1,1,'round(?(0,1023))' \

To play the Watershed Game, we -input three "images" (in the broad G'MIC sense, meaning they might not be pretty to look at).

  1. The first is the potential, or return map introduced in the previous panel. This tells us how to find the least expensive paths to a minimal point from any other point, and we concocted it in such a way that those paths will meander like rivers or lightning bolts.
  2. The second is an solid black image that we conjure out of the aether. This is the image upon which we draw rivers or lightning bolts.
  3. The third image may be hard to recognize, at first, but is a useful G'MIC idiom nontheless. It is a bag of random coordinates; we make use of the math procedure which one may furnish as a fifth argument to images. The expression 'round(?(0,1023))' runs for every pixel component in the new image. In this particular example, when G'MIC is about to set a pixel channel value, it is invited to pick some floating point value in the closed interval [0…1023], then round it to the nearest whole number. In the larger scheme of things, this gives us a coordinate pair selecting a point somewhere in our 1024 x 1024 image. We asked for 180 random coordinates. We could have asked for 65,356 for a dense pattern, or 16 for a sparse one. The particular value, 180 is a matter of taste.

From each of these 180 coordinates, we seek the minimal path to the isovalue, the one point in the potential map with a routing directive of zero.

-repeat '{{w}}' \
   rdir=0 \
   kx='{{($>,0,0,0,0)}}' \
   ky='{{($>,1,0,0,0)}}' \
   -do \
      -set[-2] '{{-2,($kx,$ky,0,0,0)}+1}','{$kx}','{$ky}',0,0 \
      rdir='{{-3,($kx,$ky,0,0,0)}}' \
      -if '{$rdir&1}' \
      kx='{$kx-1}' \
      -elif '{$rdir&2}' \
      kx='{$kx+1}' \
      -endif \
      -if '{$rdir&4}' \
      ky='{$ky-1}' \
      -elif '{$rdir&8}' \
      ky='{$ky+1}' \
      -endif \
      -while '{$rdir>0}' \
   -done \

And this, in its entirety, is the Watershed Game: We iterate over our 180 points, and for each randomly chosen coordinate, trace the minimal path from it to the isovalue. The tracing is an example of a -do … -while block. While routing directives remain non-zero, we step to the pixel the directive indicates and read the next directive. We keep on doing that until we read the zero directive, which stops the iteration. This gives us one meandering river, or lightning bolt, depending on how you want to interpret it.

The body of the -do … -while loop concerns itself with parsing the routing directive, with the current directive retained in $rdir. We initialize $kx and $ky with the current randomly chosen coordinate, then successively mask $rdir against powers of two, scanning $rdir's bit fields, and incrementing or decrementing $kx and $ky respectively, as required. We don't bother with the depth field in this case as our rivers/lightning bolts are flat, but we could. In fact, there is a G'MIC command which is a complete implementation of the partial effort here: see -minimal_path.

There are some artistic things we do as side effects. When stepping onto a pixel, we mark our passage by bumping its intensity by one, to wit: -set[-2] '{{-2,($kx,$ky,0,0,0)}+1}','{$kx}','{$ky}',0,0. The two or three inexpensive approaches to the isovalue tend to be shared by many minimal paths; these become very nearly white, while the less traveled segments remain almost black.

The line which increments the pixel's intensity illustrate another substitution sequence. A comma-separated list of complete image coordinates, width, height, depth, slice, and channel, fetches the current pixel value at that locale, here, on the penultimate position (-2) of the image stack.

-rm[-3,-1] \
-add[-1] 1e-10 \
-log[-1] \
-div[-1] '{log($pcnt)}' \
-cut -1,'{iM}' \
-normalize[-1] 0,255 \
-dilate[-1] 3 \
-repeat 2 \
-smooth[-1] 10,0.2,0.8,5,3 \
-done \
-normalize[-1] 0,255 \
-output[-1] lbolt.png \

Once we have plotted our 180 paths, we rescale the the intensity levels according to a logarithmic scale so that the 'less traveled' paths are more readly visible. We dilate, then anisotropically smooth the pathways. We used pretty aggressive smoothing here, giving flame-like bolts, but our choices here are a matter of taste. Less aggressive smoothing lends itself to more jagged electrical-like bolts.

Garry Osgood