tl;dr

We show the first steps towards reducing a logo to first principles, step-by-step. You can find the code examples in the algorithmic-metamorphant-logo github repo tag first-blog-article.

Starting point: The logo as SVG

We start with our awesome metamorphant logo and put it on a grid with exactly 10 subdivisions.

metamorphant logo on a grid

Yes, you have seen it right. In its current form, the logo is not even 100% quadratic. Let’s not let this bother us - for now.

We will need this gridded logo as a starting point to manually create a simplified SVG.

The goal: A logo derived from rules

The current logo is a complex assembly of paths with precise vertex positions. This representation is far too complex and inflexible for playful use, e.g. animation.

From first sight you can recognize, that the logo is built from some simple building blocks and patterns. For example, all corners use the same arc. All lines have the same width. All dots are circular and have the same radius.

Designer Kazi Mohammed Erfan demonstrated in his 25 creative logos study how simple principles like the golden ratio yield beautiful and appealing logos. Our goal will be the opposite: We already have a great logo. We want to reverse-engineer its underlying principles. In the end, only a very limited number of parameters should enter the equation of generating the logo.

Creating a rough SVG version

As a first step, we want to create a manual SVG description of the logo. Precision does not matter. This exercise is about choosing and leveraging the right features of SVG to reveal the hidden structure in the logo.

For simplicity’s sake we choose a 1000x1000 coordinate system. This makes for an easy translation from the logo grid created above.

We start by translating the building blocks into SVG elements:

  • The dots will be derived from the same named circle #bubble
  • All lines will be proper SVG paths
  • The dots at the end of lines will be named markers
  • The arcs on the lines will be represented using SVG path arc sections instead of curves
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="-20 -20 1040 1040" xmlns:xlink="http://www.w3.org/1999/xlink">      
  <defs>
    <circle id="bubbel" cx="0" cy="0" r="50" fill="black"/>
    <marker id="bm" viewBox="0 0 100 100" refX="50" refY="50" markerUnits="strokeWidth" markerWidth="1.667" markerHeigth="1.667">
      <use x="50" y="50" xlink:href="#bubbel" />
    </marker>
  </defs>
  
  <path d="M 390 740
           L 390 950
           L 250 950
           A 200 200 0 0 1 50 750
           L 50 250
           A 200 200 0 0 1 250 50
           L 750 50
           A 200 200 0 0 1 950 250
           L 950 750
           A 200 200 0 0 1 750 950" stroke="black" pathLength="1000" stroke-width="60" stroke-linejoin="round" fill="none" marker-start="url(#bm)" marker-end="url(#bm)" />

  <path d="M 390 180
           L 390 300
           A 200 200 0 0 0 590 500
           L 710 500
           L 710 750
           A 200 200 0 0 1 510 950" stroke="black" pathLength="1000" stroke-width="60" stroke-linejoin="round" fill="none" marker-start="url(#bm)" marker-end="url(#bm)" />

  <use x="730" y="270" xlink:href="#bubbel" />
</svg>

The result is a SVG picture that is structurally equivalent with our logo:

simplified SVG metamorphant logo draft

We are intentionally a bit sloppy here, so you can spot the difference between pseudo logo draft and real logo.

Converting to clojure dali

In order to generate this SVG from first principles, we need a more powerful language than plain SVG. We choose Clojure for that purpose, as it is simple, concise and powerful. Instead of creating the SVG directly using e.g. org.clojure/data.xml or clojure.xml we want a library in the spirit of hiccup, but for SVG. The closest we have found is dali.

First we will just be reproducing the SVG above faithfully from clojure code in hiccup-style syntax.

(ns genlogo
  (:require [dali.io]))

(def logo
  [:dali/page {:width "100%" :height "100%" :view-box "-20 -20 1040 1040"}
   [:defs
    [:circle {:id "bubbel" :cx 0 :cy 0 :r 50 :fill "black"}]
    [:marker {:id "bm" :view-box "0 0 100 100" :ref-x 50 :ref-y 50 :marker-units "strokeWidth" :marker-width 1.667 :marker-height 1.667}
     [:use {:x 50 :y 50 :xlink:href "#bubbel"}]]]
   [:path {:stroke "black" :path-length 1000 :stroke-width 60 :stroke-linejoin "round" :fill "none" :marker-start "url(#bm)" :marker-end "url(#bm)"}
    :M [390 740]
    :L [390 950]
    :L [250 950]
    :A [200 200] 0 false true [50 750]
    :L [50 250]
    :A [200 200] 0 false true [250 50]
    :L [750 50]
    :A [200 200] 0 false true [950 250]
    :L [950 750]
    :A [200 200] 0 false true [750 950]]
   [:path {:stroke "black" :path-length 1000 :stroke-width 60 :stroke-linejoin "round" :fill "none" :marker-start "url(#bm)" :marker-end "url(#bm)"}
    :M [390 180]
    :L [390 300]
    :A [200 200] 0 false false [590 500]
    :L [710 500]
    :L [710 750]
    :A [200 200] 0 false true [510 950]]
   [:use {:x 730 :y 270 :xlink:href "#bubbel"}]])

(defn -main []
  (dali.io/render-svg logo "metamorphant.svg"))

Let us create the SVG:

lein run

Surprise! It looks as before.

simplified SVG metamorphant logo draft

Refactoring to parameters and calculated values

As next step we take the existing design and by pure refactoring reduce it to the simple set of inputs:

  • radius of the dots
  • width of the lines
  • position of the eye (x, y)
  • position of the inner dot starting the outline (x, y)
  • arc radius of the corners
  • position of the inner dot starting the ear (y only, as x is same as the other inner dot)
  • position of the bottom middle dot ending the ear line (x only)

We end up with something like:

...
(def logo
  (let [dot-radius 50
        line-width 60
        eye {:x 730 :y 270}
        inner-outline-dot {:x 390 :y 740}
        corner-arc-radius 200
        inner-ear-dot {:x (:x inner-outline-dot) :y 180}
        bottom-middle-dot {:x 510}
        line-padding 50
        canvas {:width 1000 :height 1000}
        dot-diameter (* 2 dot-radius)]
      [:dali/page {:width "100%" :height "100%" :view-box "-20 -20 1040 1040"}
       [:defs
        [:circle {:id "bubbel" :cx 0 :cy 0 :r dot-radius :fill "black"}]
        [:marker {:id "bm" :view-box (str "0 0 " dot-diameter " " dot-diameter) :ref-x dot-radius :ref-y dot-radius :marker-units "userSpaceOnUse" :marker-width dot-diameter :marker-height dot-diameter}
         [:use {:x dot-radius :y dot-radius :xlink:href "#bubbel"}]]]
       [:path {:stroke "black" :path-length 1000 :stroke-width line-width :stroke-linejoin "round" :fill "none" :marker-start "url(#bm)" :marker-end "url(#bm)"}
        :M [(:x inner-outline-dot) 
            (:y inner-outline-dot)]
        :L [(:x inner-outline-dot) 
            (- (:height canvas) line-padding)]
        :L [(+ corner-arc-radius line-padding) 
            (- (:height canvas) line-padding)]
        :A [corner-arc-radius 
            corner-arc-radius]
           0 false true 
           [line-padding 
            (- (:height canvas) corner-arc-radius line-padding)]
        :L [line-padding 
            (+ corner-arc-radius line-padding)]
        :A [corner-arc-radius 
            corner-arc-radius] 
           0 false true 
           [(+ corner-arc-radius line-padding)
            line-padding]
        :L [(- (:width canvas) corner-arc-radius line-padding)
            line-padding]
        :A [corner-arc-radius
            corner-arc-radius]
           0 false true 
           [(- (:width canvas) line-padding)
            (+ corner-arc-radius line-padding)]
        :L [(- (:width canvas) line-padding)
            (- (:height canvas) corner-arc-radius line-padding)]
        :A [corner-arc-radius 
            corner-arc-radius]
           0 false true 
           [(- (:width canvas) corner-arc-radius line-padding)
            (- (:height canvas) line-padding)]]
       [:path {:stroke "black" :path-length 1000 :stroke-width line-width :stroke-linejoin "round" :fill "none" :marker-start "url(#bm)" :marker-end "url(#bm)"}
        :M [(:x inner-ear-dot)
            (:y inner-ear-dot)]
        :L [(:x inner-ear-dot) 
            (- (/ (:height canvas) 2) corner-arc-radius)]
        :A [corner-arc-radius
            corner-arc-radius] 
           0 false false 
           [(+ (:x inner-ear-dot) corner-arc-radius)
            (/ (:height canvas) 2)]
        :L [(+ (:x bottom-middle-dot) corner-arc-radius)
            (/ (:height canvas) 2)]
        :L [(+ (:x bottom-middle-dot) corner-arc-radius) 
            (- (:height canvas) corner-arc-radius line-padding)]
        :A [corner-arc-radius
            corner-arc-radius] 
           0 false true 
           [(:x bottom-middle-dot)
            (- (:height canvas) line-padding)]]
       [:use {:x (:x eye) :y (:y eye) :xlink:href "#bubbel"}]]))
...

Except the switch to userSpaceOnUse absolute marker units, this was a pure refactoring. The result still looks the same:

simplified, refactored SVG metamorphant logo draft

What did we gain by this little exercise? A whole lot of flexibility! For example, we can play around with different values without having to adjust anything else.

E.g. putting in different values for dot radius:

SVG metamorphant logo experiment, dot radius = 50 SVG metamorphant logo experiment, dot radius = 75 SVG metamorphant logo experiment, dot radius = 100

or putting in different values for corner arc radius:

SVG metamorphant logo experiment, corner arc radius = 150 SVG metamorphant logo experiment, corner arc radius = 175 SVG metamorphant logo experiment, corner arc radius = 200

Half-way through towards first principles

During our journey in this article we created a first variant of an algorithmic metamorphant logo. Of course, we are not there, yet. There are still far too many input parameters. I envision

  • deriving the line padding from the canvas size and line-width
  • deriving the eye position and the inner dot positions from some proportion rules, potentially involving the golden ratio
  • deriving the bottom middle dot position by some rule of proportion between the positions of the lower vertical line and the eye

Once we have that, we can start to fine-tune our logo, maybe even optimise it beyond its current version. We will be able to animate it using pure SVG+CSS+SMIL tricks.

We will keep you updated.

A closing rant: SVG sucks

Modern browser natively supporting an open vector graphics format like SVG is a big step forward. However, while playing around with this little algorithmic graphics exercise I had to face once more just how insufficient SVG is. It is just a poor graphics language, when compared to other languages like PostScript. It also lacks established higher-level DSL tooling. Everyone having used either of PSTricks (my favorite), PGF/TikZ or Asymptote will agree.

Maybe for the future I will have to watch out for inspiration in other vector scripting DSL libraries like paper.js, PyX, Elm GraphicsSVG, Elm Collage or Quil. In the worst case I will have to create something like a SVGtricks library myself.



Post header background image by Evgeni Tcherkasski from Pixabay.


Contact us