Randomly generated avatars

Randomly generated avatars

As a follow-up from an earlier article regarding the update to randomly generated default avatars for Passwordless.ID, I wanted to post a "how to". This is a beginner tutorial since making such avatars is actually really simple.

TL;DR; Here is the full demo.

The image format

The first thing you should think about is the image format, usually, one of:

  • Jpeg: great for real user photos due to the high compression ratio. However, this compression also produces some "blur" on lines and sharp edges. As such it is not ideal for the avatars we are going to make.

  • PNG: theses have lossless compression. In other words, every pixel remains exactly the same as it was originally drawn. Edges and lines remain "sharp".

  • SVG: these are scalable vector graphics. Unlike a "raster of pixels", it is a declarative format describing shapes and paths.

Of course, you could also save it as a "100% quality" Jpeg to avoid any quality loss, but then it is larger than PNGs. Jpeg compression is amazing though for common photos.

In our case, we picked SVG for the upcoming avatar pictures. In the past, SVG was kind of avoided because support was not always well supported for all software platforms. This is however largely in the past.

SVG offers several benefits: the first is being scalable. Due to its vector nature, it is perfectly sharp at any scale, even if you zoom in on a 4K display. Other "raster" formats like Jpeg or PNG become "pixelated" when zooming in. The other is being more compact. While the byte size of Jpeg/PNG grows with picture size, SVG grows proportional to the shape's complexity. For relatively simple stuff like the avatars here, they are super compact.

The SVG "template"

SVG is an XML format that describes the shapes. As such, what will be generated is a big XML string. To be more exact, we will fill the template below with the appropriate values.

  <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
    <defs>
      <linearGradient id="gradient" x1="${startX}" y1="${startY}" x2="${endX}" y2="${endY}">
        <stop offset="10%" stop-color="hsl(${startHue}, 100%, 50%)" />
        <stop offset="90%" stop-color="hsl(${endHue}, 100%, 50%)" />
      </linearGradient>
    </defs>
    <rect x="0" y="0" width="100" height="100" fill="url(#gradient)" />
    <text x="50" y="55" text-anchor="middle" dominant-baseline="middle" font-size="75" font-family="Times New Roman" fill="#ffffff">${char}</text>
  </svg>

Once this template is filled with meaningful values, you will obtain an avatar SVG image that can be stored as a plain normal ".svg" file.

Alternatively, you also deliver it as "data URL" since it is quite compact. This simply means encoding the resource directly instead of a "plain URL" fetching it. It is composed of two parts: the mime-type (image/svg+xml in this case) and the (base64 encoded) data. This can be used like any other URL in the src tag of an image as follows.

<img src="data:image/svg+xml;base64,{{the-base64-encoded-svg}}" />

Voilà, you got your image!

Getting some random values

The missing step is now filling this SVG template with some random values. Alternatively, if you want something more deterministic, you could also use the hash value of the name for example.

As you saw in the SVG template, instead of using RGB colors, HSL colors were used. This stands for Hue-Saturation-Lightness. This makes it easy to generate bright colors from all rainbow colors, with maximal color saturation and average "lightness".

  // Gradient colors
  const startHue = Math.round(Math.random() * 360);
  const endHue   = Math.round(Math.random() * 360);

  // Gradient direction
  const angle = Math.random() * 2 * Math.PI

  // Calculate the start and end points of the gradient
  const startX = (Math.cos(angle) + 1) / 2;
  const startY = (Math.sin(angle) + 1) / 2;
  const endX = 1 - startX;
  const endY = 1 - startY;

  // The character to appear on the avatar
  const char = name.charAt(0).toUpperCase()

For the gradient direction, it's a bit more tricky since an angle cannot be provided directly. There are some "transforms" available, but to ensure the widest compatibility with SVG renderers, sticking to the basics seems a safe bet. As such, the angle is converted to starting and ending coordinates for the gradient.

Thank you

The resulting full source code can be seen in the example provided at the beginning. :)

A Pen by Arnaud Dagnelies (codepen.io)