Home

Flip, Invert, and Reverse

13 November 2021

In this article, I'm going to introduce 3 kinds of transformations for the SVG path commands. Specifically for h/H and v/V commands.

The h/H and v/V commands draw lines horizontally and vertically. The lowercase letter specifies relative coordinates, while the uppercase letter specifies absolute coordinates.

<svg viewBox="0 0 16 16">
  <path
    stroke="#000"
    stroke-width="1"
    fill="none"
    d="M 2 2 v 4 h 8 v 4 h -3 v 4 h 8"
  />
</svg>

In order to demonstrate how these transformations work, I'll use the initial SVG path below as an example.

d: M 7 7 /* start at 7 7 */
   h 3   /* go right for 3 unit */
   v 3   /* go down for 3 unit */
   h 3   /* go right for 3 unit */
   v -3  /* go up for 3 unit */ 

Flip

The flip transformation consists of two separate operations, flip vertically and horizontally. If both operations are applied, each number that is following the v or h command will be multiplied by -1.

@flip(h 3 v 3 h 3 v -3)
   = (h -3 v -3 h -3 v 3)

Flip vertically with flipv.

@flipv(h 3 v 3 h 3 v -3)
    = (h 3 v -3 h 3 v 3)

Flip horizontally with fliph.

@fliph(h 3 v 3 h 3 v -3)
    = (h -3 v 3 h -3 v -3)

Invert

The invert transformation swaps the v and h command names with each other. After that, v becomes h and h becomes v.

@invert(h 3 v 3 h 3 v -3)
     = (v 3 h 3 v 3 h -3)

Reverse

The reverse transformation changes the given command list in a reverse order.

@reverse(h 3 v 3 h 3 v -3)
      = (v -3 h 3 v 3 h 3)

Furthermore, these basic transformations can be combined together.

@flip(@reverse(h 3 v 3 h 3 v -3))
       = @flip(v -3 h 3 v 3 h 3)
            = (v 3 h -3 v -3 h -3)

SVG and css-doodle

Flip, invert and reverse have been implemented in CSS Doodle already but once you get the idea it'll be easy to implement and add them to your own toolset.

CSS Doodle has a simplified syntax for function composition, which will save a couple of parentheses when two or more functions are joined together.

@invert(@flip(@reverse(h 3 v 3 h 3 v -3)))

/* can be written as: */

@invert.@flip.@reverse(h 3 v 3 h 3 v -3)

There's also a new syntax for writing SVG, but that's another topic. It looks more natural in CSS without quotes around attribute values.

@svg(
  viewBox: 0 0 16 16;
  path {
    stroke: #000;
    stroke-width: 1;
    fill: none;
    d: M 8 8 @flip(h 3 v 3 h 3 v -3);
  }
)

Traditional Chinese border pattern

I'm always fascinated by the traditional border patterns. The three transformation methods are what I got when I wanted to draw the patterns in the first place. I'll show the steps of how to draw the pattern as shown below.

First, prepare the basic code structure and add the outside frame so that I can see where the boundary is.

<css-doodle>
  @size: 200px;
  border: 3px solid #000;
  background: @svg(
    viewbox: 0 0 200 200;
    path {
      fill: none;
      stroke: #000;
      stroke-width: 3;
      stroke-linecap: square;

      d: /* path commands */
    }
  );
</css-doodle>

Next, draw the base path, which starts from the middle top position (100, 5) and ends at the position (5, 5)

d: M 100 5
   h-43 v5 h-10 v-5 h-10 v16 h-32 v10 h10 v-26 h-10

Get new path commands by transforming the base path and append them to the end. Here the base path is wrapped by function @p so that it can be referenced later with @lp.

d: M 100 5
   @p(h-43 v5 h-10 v-5 h-10 v16 h-32 v10 h10 v-26 h-10)
   @flip.@invert.@reverse.@lp

Treat the drawn paths as a whole and do invert and flipv transformations to it.

d: M 100 5
   @p(@p(h-43 v5 h-10 v-5 h-10 v16 h-32 v10 h10 v-26 h-10)
      @flip.@invert.@reverse.@lp)
   @flipv.@invert.@lp

Since now the point is at the bottom center, It'll need to flip both horizontally and vertically to form the final graph.

d: M 100 5
   @p(@p(@p(h-43 v5 h-10 v-5 h-10 v16 h-32 v10 h10 v-26 h-10)
         @flip.@invert.@reverse.@lp)
      @flipv.@invert.@lp)
   @flip.@lp

Although there are different other ways to generate the pattern, this is by far the shortest version I'm able to get. See also on CodePen.

Hilbert curve

With the same method and process, we can draw a simple version of the Hilbert curve.

Starting from the bottom left, the base path could be:

d: M 1 16
   h1 v-1 h-1 v-2 h1 v1 h1 v-1 h1 v2 h-1 v1 h2

And then do some transformations to form a more complex graph.

d: M 1 16
   @p(h1 v-1 h-1 v-2 h1 v1 h1 v-1 h1 v2 h-1 v1 h2)
   @flip.@invert.@lp v1
   @reverse.@invert.@fliph.@lp
   @reverse.@fliph.@lp

Repeat the last step.

d: M 1 16
   @p(@p(@p(h1 v-1 h-1 v-2 h1 v1 h1 v-1 h1 v2 h-1 v1 h2)
         @flip.@invert.@lp v1
         @reverse.@invert.@fliph.@lp
         @reverse.@fliph.@lp)
      v-1 @invert.@flip.@lp)
   h1 @reverse.@flipv.@lp

The process can go on and on to generate larger dimension versions of the curve. See also on CodePen.

Cycle carré de méandre

The last example is from the work of computer art pioneer Vera Molnár. It looks very complicated at first, but certain rules can be found in the process of coding by hand. See also on CodePen.

d: M 2 3
   @p(@p.@reverse(h1 @M19(@pn(v,h) @calc(@pn(-2,-2,2,2)*@n)) h38)
      @reverse.@lp)
   h2 v-40 h2          @lp
   h2 v-40 h2          @lp
   h2 v-40 h2 v82 h-2  @flip.@lp

   h-2 v40 h-2         @flip.@lp
   h-2                 @fliph.@lp
   h-2 v-40 h-2 v40 v2 @lp

   h2 v-40 h2          @lp
   h2 v-40 h2          @lp
   h2 v-40 h2 v40

Hint

The reverse transformation is a key part of the base path.

d: M 2 3
   @reverse(
     h1 @M19(@pn(v,h) @calc(@pn(-2,-2,2,2)*@n)) h38)

Now fill the other half with reverse.

d: M 2 3
   @p.@reverse(
     h1 @M19(@pn(v,h) @calc(@pn(-2,-2,2,2)*@n)) h38)
   @reverse.@lp

Final words

These transformations can be replaced with rotate in one way or another, however, the transform-origin will be involved then and the result must be put into different path elements. Sometimes a single path is more convenient for animation.

Writing SVG path by hand can be hard, but it's still fun to find new ways of doing old things. Next time I'll see if the same concept can be applied to curves.