Dynamically masking video using canvas and SVG
Tags: javascript, canvas, svg
Published: 2020-12-15 01:19:19

# TL;DR what we're building:

{% codepen https://codepen.io/benjaminblack/pen/BaLWPae %}


---

# Part 1: Masking a static image on a canvas

Let's start with a canvas, an image to use as a matte background, and an SVG snowflake to use as a mask, plus some simple CSS to keep it clean.

```html
<canvas width="960" height="540"></canvas>

<img src="https://images.unsplash.com/photo-1607714724990-8f5495b75883?q=30" width="1875" height="2502">

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 432 432">
  <path fill="#fff" d="M364 318l35 4-21-29-24-2-4-2 19-5a4 4 0 00-2-9l-27 7-33-19 7-2 24 14a4 4 0 104-7l-16-10 4-1 24 4 28-22-35-6-20 16-3 1 9-17a4 4 0 10-7-4l-14 24-16 4-64-37h33l20 20a4 4 0 106-6l-14-14h4l23 10 32-14-32-14-23 10h-4l14-14a4 4 0 10-6-6l-20 20h-33l64-37 16 4 14 24a4 4 0 107-4l-9-17 3 1 20 16 35-6-28-22-24 4-4-1 16-10a4 4 0 00-4-7l-24 13-7-1 33-19 27 7a4 4 0 102-9l-19-5 4-2 24-2 21-29-35 4-15 20-3 2 5-18a4 4 0 10-8-3l-8 27-32 19 1-7 24-14a4 4 0 00-4-8l-16 10 1-4 15-19-5-35-23 28 4 24-1 4-9-17a4 4 0 10-8 5l14 24-4 15-64 37 16-28 27-7a4 4 0 00-2-9l-19 5 2-3 20-15 4-35-29 21-2 24-2 4-5-19a4 4 0 10-8 3l7 26-17 29v-74l12-12h27a4 4 0 000-8h-19l3-3 23-9 13-33-33 13-9 23-3 3V81a4 4 0 10-8 0v27l-6 6V76l20-20a4 4 0 10-6-6l-14 13v-4l10-22-14-33-14 33 10 22v4l-14-13a4 4 0 10-6 6l20 20v38l-6-6V81a4 4 0 00-8 0v19l-3-3-9-23-33-13 13 33 23 9 3 3h-19a4 4 0 000 8h27l12 12v74l-17-29 7-26a4 4 0 00-8-3l-5 19-2-4-2-24-29-21 4 35 20 15 2 3-19-5a4 4 0 10-2 9l27 7 16 28-64-37-4-15 14-24a4 4 0 00-8-5l-9 17-1-4 4-24-23-28-5 35 15 19 1 4-16-10a4 4 0 10-4 8l24 14 1 7-32-19-8-27a4 4 0 10-8 3l5 18-3-2-15-20-35-4 21 29 24 2 4 2-19 5a4 4 0 002 9l27-7 33 19-7 1-24-13a4 4 0 10-4 7l16 10-4 1-24-4-28 22 35 6 20-16 3-1-9 17a4 4 0 107 4l14-24 16-4 64 37h-33l-20-20a4 4 0 10-6 6l14 14h-4l-23-10-32 14 32 14 23-10h4l-14 14a4 4 0 106 6l20-20h33l-64 37-16-4-14-24a4 4 0 00-7 4l9 17-3-1-20-16-35 6 28 22 24-4 4 1-16 10a4 4 0 004 7l24-14 7 2-33 19-27-7a4 4 0 00-2 9l19 5-4 2-24 2-21 29 35-4 15-20 3-2-5 18a4 4 0 008 3l8-27 32-19-1 7-24 14a4 4 0 004 8l16-10-1 4-15 19 5 35 23-28-4-24 1-4 9 17a4 4 0 008-5l-14-24 4-15 64-37-16 28-27 7a4 4 0 102 9l19-5-2 3-20 15-4 35 29-21 2-24 2-4 5 19a4 4 0 108-3l-7-26 17-29v74l-12 12h-27a4 4 0 100 8h19l-3 3-23 9-13 33 33-13 9-23 3-3v19a4 4 0 008 0v-27l6-6v38l-20 20a4 4 0 006 6l14-13v4l-10 22 14 33 14-33-10-22v-4l14 13a4 4 0 006-6l-20-20v-38l6 6v27a4 4 0 008 0v-19l3 3 9 23 33 13-13-33-23-9-3-3h19a4 4 0 000-8h-27l-12-12v-74l17 29-7 26a4 4 0 008 3l5-19 2 4 2 24 29 21-4-35-20-15-2-3 19 5a4 4 0 102-9l-27-7-16-28 64 37 4 15-14 24a4 4 0 108 5l9-17 1 4-4 24 23 28 5-35-15-19-1-4 16 10a4 4 0 004-8l-24-14-1-7 32 19 8 27a4 4 0 108-3l-5-18 3 2 15 20z"/>
</svg>
```

```css
* { box-sizing: inherit; }

html, body {
  margin: 0;
}

html {
  box-sizing: border-box;
  padding: 0;
  color: white;
  background-color: black;
}

body {
  padding: 1rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1em;
}

canvas {
  width: 100%;
  height: auto;
}

img, svg {
  width: 75vmin;
  height: auto;
}
```

Since we're waiting for the DOM (and until we get [top-level await](https://www.chromestatus.com/feature/5767881411264512)), we'll wrap our JS in an async `main` function and call it after `DOMContentLoaded` is fired on the window.

```javascript
async function main() {

}

self.addEventListener('DOMContentLoaded', () => main().catch(console.error), {once: true});
```

For now, we'll just dump our code in `main`.

Let's begin by declaring some refs that we'll need.

```javascript
const canvas = document.querySelector('canvas');
const canvasContext = canvas.getContext('2d');
const canvasAspectRatio = canvas.width / canvas.height;

const matte = document.querySelector('img');
```

We can't draw an inline `<svg>` directly on the canvas, so we'll convert it to an `Image`, and then wait for both the matte and mask images to load before proceeding.

```javascript
const mask = document.createElement('img');

mask.src = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(document.querySelector('svg'))], {type: 'image/svg+xml'}));

await Promise.all([
  new Promise((resolve) => mask.addEventListener('load', resolve, {once: true})),
  matte.complete || new Promise((resolve) => matte.addEventListener('load', resolve, {once: true})),
]);
```

Once both images are loaded, we can calculate their aspect ratios and declare some variables we'll need to position them on the canvas.

```javascript
const matteAspectRatio = matte.naturalWidth / matte.naturalHeight, maskAspectRatio = mask.naturalWidth / mask.naturalHeight;

let width, height, top, left;
```

Next, we'll draw the matte background on the canvas, scaling and positioning it so that it [covers](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) the canvas.

```javascript
[width, height] =
  canvasAspectRatio <= matteAspectRatio
  ? [canvas.height * matteAspectRatio, canvas.height]
  : [canvas.width, canvas.width / matteAspectRatio];

[top, left] = [(canvas.height - height) / 2, (canvas.width - width) / 2];

canvasContext.drawImage(matte, 0, 0, matte.naturalWidth, matte.naturalHeight, left, top, width, height);
```

Now let's mask the image on the canvas using `destination-in` [compositing](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation). We'll draw the mask to fit inside the canvas, instead of filling it (simply by switching the cases of the ternary expression to calculate width and height).

```javascript
[width, height] = canvasAspectRatio <= maskAspectRatio
  ? [canvas.width, canvas.width / maskAspectRatio]
  : [canvas.height * maskAspectRatio, canvas.height];

[top, left] = [(canvas.height - height) / 2, (canvas.width - width) / 2];

canvasContext.globalCompositeOperation = 'destination-in';

canvasContext.drawImage(mask, left, top, width, height);
```

### Part 1 Pen

{% codepen https://codepen.io/benjaminblack/pen/rNMWbLg %}


---

# Part 2: Using a video instead of a matte background

Let's replace the matte background image with a video. Instead of adding a `<video>` element to the DOM, we'll keep the document clean and create the video programmatically. 

We'll use `requestAnimationFrame` to redraw every frame of the video on the canvas.

Let's wipe the `main` function and start over.

We'll remember the dimensions and position of the video and mask, instead of recalculating them on each frame, so they'll need their own variables instead of reusing `width`, `height`, `left`, and `top`.

```javascript
async function main() {
  const canvas = document.querySelector('canvas');
  const canvasContext = canvas.getContext('2d');
  const canvasAspectRatio = canvas.width / canvas.height;

  const video = document.createElement('video');

  video.muted = true;
  video.autoplay = true;
  video.loop = true;
  video.playsinline = true;
  video.src = "https://giant.gfycat.com/CheapBitterCaterpillar.webm";
  
  await video.play();
  
  const videoAspectRatio = video.videoWidth / video.videoHeight;

  const [frameWidth, frameHeight] =
    canvasAspectRatio <= videoAspectRatio
    ? [canvas.height * videoAspectRatio, canvas.height]
  : [canvas.width, canvas.width / videoAspectRatio];

  const [frameTop, frameLeft] = [(canvas.height - frameHeight) / 2, (canvas.width - frameWidth) / 2];
  
  requestAnimationFrame(function frame() {
    canvasContext.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, frameLeft, frameTop, frameWidth, frameHeight);
    requestAnimationFrame(frame);
  });
}
```

Now the video is being drawn on the canvas on every frame. Let's write the code to load the mask. We'll scale the mask to be centered and contained within the canvas.

Let's also get rid of the mask `<svg>` from the DOM, and create it dynamically in Javascript, skipping an intensive serialization step.

We also need to update the `requestAnimationFrame`, since it overwrites the canvas on each frame. We'll switch the drawing order so that the mask is drawn first, using `destination-over` compositing (to overwrite the previous frame), and then draw the video frame using `source-in` compositing. (This is to prepare for using multiple masks instead of just one.)

```javascript
async function main() {
  const canvas = document.querySelector('canvas');
  const canvasContext = canvas.getContext('2d');
  const canvasAspectRatio = canvas.width / canvas.height;

  const video = document.createElement('video');
  video.muted = true;
  video.autoplay = true;
  video.loop = true;
  video.playsinline = true;
  video.src = "https://giant.gfycat.com/CheapBitterCaterpillar.webm";

  const maskImage = document.createElement('img');
  maskImage.src = URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" width="432" height="432"><path fill="#fff" d="M364 318l35 4-21-29-24-2-4-2 19-5a4 4 0 00-2-9l-27 7-33-19 7-2 24 14a4 4 0 104-7l-16-10 4-1 24 4 28-22-35-6-20 16-3 1 9-17a4 4 0 10-7-4l-14 24-16 4-64-37h33l20 20a4 4 0 106-6l-14-14h4l23 10 32-14-32-14-23 10h-4l14-14a4 4 0 10-6-6l-20 20h-33l64-37 16 4 14 24a4 4 0 107-4l-9-17 3 1 20 16 35-6-28-22-24 4-4-1 16-10a4 4 0 00-4-7l-24 13-7-1 33-19 27 7a4 4 0 102-9l-19-5 4-2 24-2 21-29-35 4-15 20-3 2 5-18a4 4 0 10-8-3l-8 27-32 19 1-7 24-14a4 4 0 00-4-8l-16 10 1-4 15-19-5-35-23 28 4 24-1 4-9-17a4 4 0 10-8 5l14 24-4 15-64 37 16-28 27-7a4 4 0 00-2-9l-19 5 2-3 20-15 4-35-29 21-2 24-2 4-5-19a4 4 0 10-8 3l7 26-17 29v-74l12-12h27a4 4 0 000-8h-19l3-3 23-9 13-33-33 13-9 23-3 3V81a4 4 0 10-8 0v27l-6 6V76l20-20a4 4 0 10-6-6l-14 13v-4l10-22-14-33-14 33 10 22v4l-14-13a4 4 0 10-6 6l20 20v38l-6-6V81a4 4 0 00-8 0v19l-3-3-9-23-33-13 13 33 23 9 3 3h-19a4 4 0 000 8h27l12 12v74l-17-29 7-26a4 4 0 00-8-3l-5 19-2-4-2-24-29-21 4 35 20 15 2 3-19-5a4 4 0 10-2 9l27 7 16 28-64-37-4-15 14-24a4 4 0 00-8-5l-9 17-1-4 4-24-23-28-5 35 15 19 1 4-16-10a4 4 0 10-4 8l24 14 1 7-32-19-8-27a4 4 0 10-8 3l5 18-3-2-15-20-35-4 21 29 24 2 4 2-19 5a4 4 0 002 9l27-7 33 19-7 1-24-13a4 4 0 10-4 7l16 10-4 1-24-4-28 22 35 6 20-16 3-1-9 17a4 4 0 107 4l14-24 16-4 64 37h-33l-20-20a4 4 0 10-6 6l14 14h-4l-23-10-32 14 32 14 23-10h4l-14 14a4 4 0 106 6l20-20h33l-64 37-16-4-14-24a4 4 0 00-7 4l9 17-3-1-20-16-35 6 28 22 24-4 4 1-16 10a4 4 0 004 7l24-14 7 2-33 19-27-7a4 4 0 00-2 9l19 5-4 2-24 2-21 29 35-4 15-20 3-2-5 18a4 4 0 008 3l8-27 32-19-1 7-24 14a4 4 0 004 8l16-10-1 4-15 19 5 35 23-28-4-24 1-4 9 17a4 4 0 008-5l-14-24 4-15 64-37-16 28-27 7a4 4 0 102 9l19-5-2 3-20 15-4 35 29-21 2-24 2-4 5 19a4 4 0 108-3l-7-26 17-29v74l-12 12h-27a4 4 0 100 8h19l-3 3-23 9-13 33 33-13 9-23 3-3v19a4 4 0 008 0v-27l6-6v38l-20 20a4 4 0 006 6l14-13v4l-10 22 14 33 14-33-10-22v-4l14 13a4 4 0 006-6l-20-20v-38l6 6v27a4 4 0 008 0v-19l3 3 9 23 33 13-13-33-23-9-3-3h19a4 4 0 000-8h-27l-12-12v-74l17 29-7 26a4 4 0 008 3l5-19 2 4 2 24 29 21-4-35-20-15-2-3 19 5a4 4 0 102-9l-27-7-16-28 64 37 4 15-14 24a4 4 0 108 5l9-17 1 4-4 24 23 28 5-35-15-19-1-4 16 10a4 4 0 004-8l-24-14-1-7 32 19 8 27a4 4 0 108-3l-5-18 3 2 15 20z"/></svg>`], {type: 'image/svg+xml'}));

  await Promise.all([
    video.play(),
    new Promise((resolve) => maskImage.addEventListener('load', resolve, {once: true})),
  ]);
  
  const videoAspectRatio = video.videoWidth / video.videoHeight;

  const [frameWidth, frameHeight] =
    canvasAspectRatio <= videoAspectRatio
    ? [canvas.height * videoAspectRatio, canvas.height]
  : [canvas.width, canvas.width / videoAspectRatio];

  const [frameTop, frameLeft] = [(canvas.height - frameHeight) / 2, (canvas.width - frameWidth) / 2];

  const maskAspectRatio = maskImage.naturalWidth / maskImage.naturalHeight;

  const [maskWidth, maskHeight] =
        canvasAspectRatio <= maskAspectRatio
  ? [canvas.width, canvas.width / maskAspectRatio]
  : [canvas.height * maskAspectRatio, canvas.height];

  const [maskTop, maskLeft] = [(canvas.height - maskHeight) / 2, (canvas.width - maskWidth) / 2];
  
  requestAnimationFrame(function frame() {
    canvasContext.globalCompositeOperation = 'destination-over';
    canvasContext.drawImage(maskImage, maskLeft, maskTop, maskWidth, maskHeight);

    canvasContext.globalCompositeOperation = 'source-in';
    canvasContext.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, frameLeft, frameTop, frameWidth, frameHeight);

    requestAnimationFrame(frame);
  });
}
```

Now we have a video that is being dynamically masked on every frame.

Since the HTML only contains a single `<canvas>` element, let's tweak the CSS a little bit.

```css
* { box-sizing: inherit; }

html, body {
  margin: 0;
}

html {
  box-sizing: border-box;
  padding: 0;
  color: white;
  background-color: black;
  height: 100%;
}

body {
  padding: 1rem;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

canvas {
  width: 100%;
  height: auto;
}
```

### Part 2 Pen

{% codepen https://codepen.io/benjaminblack/pen/poERbBV %}


---

# Part 3: Define multiple masks and use one at random

Instead of a single big snowflake, let's cache [a bunch of different snowflake patterns](https://www.svgrepo.com/collection/snowflakes-set/) in an array. We'll create an array of promises that will resolve with a loaded mask image, and which will replace themselves in the array with the resulting Image object, so that we can use `Promise.all` on the entire array to wait for all images to load.

We'll declare it at the top-level scope, so that the images can begin loading immediately instead of waiting for `main()` to be executed.

```javascript
const masks = Array.from([
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" width="432" height="432"><path fill="#fff" d="M364 318l35 4-21-29-24-2-4-2 19-5a4 4 0 00-2-9l-27 7-33-19 7-2 24 14a4 4 0 104-7l-16-10 4-1 24 4 28-22-35-6-20 16-3 1 9-17a4 4 0 10-7-4l-14 24-16 4-64-37h33l20 20a4 4 0 106-6l-14-14h4l23 10 32-14-32-14-23 10h-4l14-14a4 4 0 10-6-6l-20 20h-33l64-37 16 4 14 24a4 4 0 107-4l-9-17 3 1 20 16 35-6-28-22-24 4-4-1 16-10a4 4 0 00-4-7l-24 13-7-1 33-19 27 7a4 4 0 102-9l-19-5 4-2 24-2 21-29-35 4-15 20-3 2 5-18a4 4 0 10-8-3l-8 27-32 19 1-7 24-14a4 4 0 00-4-8l-16 10 1-4 15-19-5-35-23 28 4 24-1 4-9-17a4 4 0 10-8 5l14 24-4 15-64 37 16-28 27-7a4 4 0 00-2-9l-19 5 2-3 20-15 4-35-29 21-2 24-2 4-5-19a4 4 0 10-8 3l7 26-17 29v-74l12-12h27a4 4 0 000-8h-19l3-3 23-9 13-33-33 13-9 23-3 3V81a4 4 0 10-8 0v27l-6 6V76l20-20a4 4 0 10-6-6l-14 13v-4l10-22-14-33-14 33 10 22v4l-14-13a4 4 0 10-6 6l20 20v38l-6-6V81a4 4 0 00-8 0v19l-3-3-9-23-33-13 13 33 23 9 3 3h-19a4 4 0 000 8h27l12 12v74l-17-29 7-26a4 4 0 00-8-3l-5 19-2-4-2-24-29-21 4 35 20 15 2 3-19-5a4 4 0 10-2 9l27 7 16 28-64-37-4-15 14-24a4 4 0 00-8-5l-9 17-1-4 4-24-23-28-5 35 15 19 1 4-16-10a4 4 0 10-4 8l24 14 1 7-32-19-8-27a4 4 0 10-8 3l5 18-3-2-15-20-35-4 21 29 24 2 4 2-19 5a4 4 0 002 9l27-7 33 19-7 1-24-13a4 4 0 10-4 7l16 10-4 1-24-4-28 22 35 6 20-16 3-1-9 17a4 4 0 107 4l14-24 16-4 64 37h-33l-20-20a4 4 0 10-6 6l14 14h-4l-23-10-32 14 32 14 23-10h4l-14 14a4 4 0 106 6l20-20h33l-64 37-16-4-14-24a4 4 0 00-7 4l9 17-3-1-20-16-35 6 28 22 24-4 4 1-16 10a4 4 0 004 7l24-14 7 2-33 19-27-7a4 4 0 00-2 9l19 5-4 2-24 2-21 29 35-4 15-20 3-2-5 18a4 4 0 008 3l8-27 32-19-1 7-24 14a4 4 0 004 8l16-10-1 4-15 19 5 35 23-28-4-24 1-4 9 17a4 4 0 008-5l-14-24 4-15 64-37-16 28-27 7a4 4 0 102 9l19-5-2 3-20 15-4 35 29-21 2-24 2-4 5 19a4 4 0 108-3l-7-26 17-29v74l-12 12h-27a4 4 0 100 8h19l-3 3-23 9-13 33 33-13 9-23 3-3v19a4 4 0 008 0v-27l6-6v38l-20 20a4 4 0 006 6l14-13v4l-10 22 14 33 14-33-10-22v-4l14 13a4 4 0 006-6l-20-20v-38l6 6v27a4 4 0 008 0v-19l3 3 9 23 33 13-13-33-23-9-3-3h19a4 4 0 000-8h-27l-12-12v-74l17 29-7 26a4 4 0 008 3l5-19 2 4 2 24 29 21-4-35-20-15-2-3 19 5a4 4 0 102-9l-27-7-16-28 64 37 4 15-14 24a4 4 0 108 5l9-17 1 4-4 24 23 28 5-35-15-19-1-4 16 10a4 4 0 004-8l-24-14-1-7 32 19 8 27a4 4 0 108-3l-5-18 3 2 15 20z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 296 296"><path d="M250 185a7 7 0 00-12 1l-2 4-13-8 19-12a7 7 0 10-8-13l-25 16-19-25 19-26 25 17a7 7 0 0010-2c3-3 2-8-2-10l-19-14 13-8 2 5a7 7 0 0012 1l26-29a7 7 0 00-8-12l-37 8a7 7 0 00-5 11l2 4-13 7-1-23a8 8 0 00-15 1l2 29-32 4-13-29 27-14c3-2 5-6 3-10s-7-5-11-3l-20 11V51h5a7 7 0 006-10L155 5c-1-3-4-5-7-5-4 0-7 2-8 5l-12 36a7 7 0 007 10h5v15l-22-10c-3-2-8-1-10 3-1 4 0 8 4 10l26 13-12 29-31-4 1-29a7 7 0 10-15-1l-1 23-12-7 2-4a8 8 0 00-5-11l-37-8a7 7 0 00-8 13l26 28a7 7 0 0012-1l2-4 13 7-20 14a8 8 0 009 12l24-16 18 25-18 25-25-16a8 8 0 00-8 13l20 12-13 8-2-4a7 7 0 00-12-1l-25 28a8 8 0 007 12l37-7a8 8 0 005-11l-2-5 13-7 1 24a7 7 0 1015-1l-2-29 31-4 12 29-26 13c-4 1-5 6-3 10 1 3 6 5 9 3l22-10v15h-5a7 7 0 00-7 10l12 36c1 3 4 5 8 5 3 0 6-2 7-5l12-36a8 8 0 00-7-10h-5v-15l20 11 4 1c3 0 5-2 7-4 2-4 0-9-3-11l-27-13 13-29 31 4-2 29a8 8 0 0015 1l2-24 13 8-2 4a7 7 0 005 11l37 8a7 7 0 008-4c2-3 2-6-1-9l-25-28z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297 297"><path d="M290 142h-12l9-10a7 7 0 10-11-10l-19 20h-14l9-10a7 7 0 10-11-10l-19 20h-13l17-19 2-5c0-4-3-7-7-7h-23l8-8h27a7 7 0 100-15h-12l10-10h27a7 7 0 100-15h-12l8-9a8 8 0 00-11-10l-8 8V39c0-4-4-8-8-8s-7 4-7 8v28l-10 10V64a8 8 0 00-15 0l1 27-9 9V78c0-3 0-5-2-6-3-3-8-4-11-1l-19 17V74l20-18c3-3 3-8 0-11-2-3-7-3-10 0l-10 9V40l20-19c3-3 3-8 0-11s-7-3-10 0l-10 9V8a7 7 0 10-15 0v10l-8-8c-3-3-7-3-10 0s-3 8-1 10l19 19v14l-8-8c-3-3-7-3-10 0s-3 8-1 10l19 19v13l-17-16a7 7 0 00-12 6l-1 22-8-8V65a7 7 0 10-15 0v11L77 66V40a7 7 0 10-15 0v11l-8-8c-3-3-7-3-10 0s-3 8 0 11l8 8H39c-4 0-7 4-7 8s3 7 7 7h28l10 10H64a7 7 0 100 15l27-1 10 10H78l-7 2c-3 3-3 8 0 11l1 1 16 17H74l-18-20a7 7 0 10-11 10l9 9H40l-19-19a7 7 0 10-11 10l9 9H8a7 7 0 100 15h10l-8 9a7 7 0 1010 10l19-18h14l-8 8a7 7 0 1010 10l19-18h13l-16 16a7 7 0 005 13l24 1-9 9-27-1a8 8 0 000 15l12 1-10 10-27-1c-4 0-7 4-8 8 0 4 4 7 8 7h13l-8 8a8 8 0 005 13l5-2 7-7v12a7 7 0 1015 0v-27l10-10v12a7 7 0 1015 0v-27l10-10v23a8 8 0 008 8c2 1 4-1 6-2l15-16v12l-18 19c-3 3-3 8 0 11l5 2 5-2 8-9v14l-18 19c-3 3-3 8 0 11l5 2 5-2 8-9v12a7 7 0 1015 0v-12l10 9a8 8 0 0010 0c3-3 3-8 0-11l-20-19v-14l10 9a8 8 0 0010 0c3-3 3-8 0-11l-20-19v-13l17 17 7 2c4 0 7-3 7-7v-25l10 10-1 27a7 7 0 1015 1l1-13 10 9-1 28c0 4 3 7 7 7l1 1c4 0 7-4 7-8v-13l8 8a7 7 0 0011 0c3-3 3-7 0-10l-7-7h11a7 7 0 100-15h-26l-10-10h11a7 7 0 100-15h-26l-9-10 23-1 5-2c3-3 3-7 0-10l-16-16h12l19 18a7 7 0 0011 0c3-3 3-7 0-10l-9-8h14l19 18a7 7 0 0011 0c3-3 3-7 0-10l-9-8h12a7 7 0 100-15zm-175-17l17 17h-24l-14-16 21-1zm0 47H94l14-16h23l-16 16zm25 17l-14 14-1-20 15-15v21zm0-60l-15-15V94l15 14v21zm15-21l17-15v22l-17 17v-24zm16 96l-16-15v-24l16 17v22zm12-32l-15-15h21l14 14-20 1zm6-30h-22l16-17h21l-15 17z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 298 298"><path d="M291 142h-12v-11a7 7 0 10-15 0v11h-9v-17a7 7 0 10-15 0v17h-69l-2-3 50-49 12 12a7 7 0 0011 0c3-3 3-8 0-11l-12-12 6-6 8 7a7 7 0 0010 0c3-3 3-7 0-10l-7-8 7-8a8 8 0 00-10-10l-8 7-8-7a8 8 0 00-10 10l7 8-6 6-12-12a8 8 0 00-11 11l12 12-50 50-2-1V58h18a7 7 0 100-15h-18v-9h12a7 7 0 100-15h-12V8a7 7 0 10-15 0v11h-10a7 7 0 100 15h10v9h-16a7 7 0 100 15h16v70l-1 1-50-50 12-12a7 7 0 10-11-11L79 68l-6-6 7-8a8 8 0 00-10-10l-8 7-8-7a8 8 0 00-10 10l7 8-7 8a8 8 0 1010 10l8-7 6 6-12 12a7 7 0 1011 11l12-12 50 50-1 1H57v-16a7 7 0 10-15 0v17h-9v-11a7 7 0 10-15 0v11H8a7 7 0 100 15h10v11a7 7 0 1015 0v-11h9v17a7 7 0 1015 0v-17h71l1 2-50 49-12-12a8 8 0 00-11 11l12 12-6 6-8-7a7 7 0 10-10 10l7 8-7 8a8 8 0 1010 10l8-7 8 7a7 7 0 0010 0c3-3 3-7 0-10l-7-8 6-6 12 12a7 7 0 0011 0c3-3 3-8 0-11l-12-12 49-50 2 2v70h-16a7 7 0 100 15h16v9h-10a7 7 0 100 15h10v11a7 7 0 1015 0v-11h12a7 7 0 100-15h-12v-9h18a7 7 0 100-15h-18v-70l3-1 49 49-12 12a8 8 0 0011 11l12-12 6 6-7 8a8 8 0 0010 10l8-7 8 7a7 7 0 0010 0c3-3 3-7 0-10l-7-8 7-8a8 8 0 00-10-10l-8 7-6-6 12-12a8 8 0 00-11-11l-12 12-49-49 1-3h69v17a7 7 0 1015 0v-17h9v11a7 7 0 1015 0v-11h12a7 7 0 100-14z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297 297"><path d="M290 133c-4-5-10-7-16-7a22 22 0 00-21 15h-20l8-8a8 8 0 00-10-11l-19 19h-22l-8-16 17-16h27a7 7 0 100-15h-12l14-14a22 22 0 0025-5 23 23 0 00-16-38 22 22 0 00-22 22c0 4 0 7 2 10l-14 14V72a7 7 0 10-15 0v26l-16 16-16-6V86l19-20c3-3 3-7 0-10s-8-3-11 0l-8 8V44c9-3 15-12 15-21 0-13-10-23-23-23-12 0-22 10-22 23 0 9 7 18 15 21v20l-9-8c-3-3-8-3-11 0-2 3-3 7 0 10l20 20v21l-18 6-14-15V71a7 7 0 10-15 0v12L80 69a22 22 0 00-21-32 22 22 0 00-22 22 22 22 0 0022 23l10-2 14 14H72a7 7 0 100 15h26l15 14-8 18H85l-19-19a8 8 0 00-11 10l9 9H44a23 23 0 100 15h20l-8 8a7 7 0 1010 11l19-19h21l6 18-14 14H71a7 7 0 100 15h12l-14 14-10-2a22 22 0 00-22 22 22 22 0 0022 23 22 22 0 0021-32l14-14v11a7 7 0 1015 0v-26l14-15 18 7v21l-20 19c-2 3-2 8 1 11l5 2 5-2 9-9v20c-8 3-15 12-15 21a23 23 0 0045 0c0-9-6-18-15-21v-20l8 8a8 8 0 0011 0c3-3 2-8-1-11l-18-18v-22l16-7 16 15v28a7 7 0 1015 0v-13l14 15c-2 3-2 6-2 9a22 22 0 0022 23 22 22 0 0023-23 22 22 0 00-23-22l-9 2-14-14h11a7 7 0 100-15h-26l-16-16 7-16h22l19 19a7 7 0 0011 0c3-3 3-7 0-10l-9-9h20a23 23 0 1037-23zm-122 36l-21 9-20-9-8-21 8-20 20-8 20 9 10 19-9 21z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 298 298"><path d="M290 141h-25l11-12a7 7 0 10-11-10l-21 22h-37l16-18a8 8 0 00-4-12h-22l24-24h31a7 7 0 100-15h-16l18-18c3-3 3-8 0-11s-8-3-11 0l-18 18V46a7 7 0 10-15 0v30l-25 25V78l-2-5c-3-3-8-3-11 0l-16 15V53l22-21c3-3 3-8 0-11s-8-3-11 0l-11 11V8a7 7 0 10-15 0v24l-10-11c-3-3-8-3-11 0s-3 8 0 11l21 21v36l-16-16a7 7 0 00-12 4l-1 2v21L87 75V46a7 7 0 10-15 0v14L55 43c-3-3-7-3-10 0s-3 8 0 11l17 18H47a7 7 0 100 15h30l25 24-23 1c-4 0-7 4-7 7l-1 1h1c0 2 0 4 2 5l16 16H55l-22-22a8 8 0 00-10 10l11 11H9a7 7 0 100 15h25l-11 11a8 8 0 1010 11l22-21h35l-15 16a8 8 0 005 13v-1 1l22-1-25 26H47a7 7 0 100 15h15l-17 17a8 8 0 005 13l5-2 17-18v16a7 7 0 1015 0v-31l26-25 1 24c0 4 3 8 7 8l4-2 16-16v36l-21 21c-3 3-3 7 0 10l6 3c2 0 3-1 5-3l10-10v25a7 7 0 1015 0v-26l12 11a8 8 0 0010 0c3-3 3-7 0-10l-22-22v-36a5625 5625 0 0023 19c4 0 7-4 7-8v-24l24 25v31a7 7 0 1015 0v-16l18 18a8 8 0 0011 0c3-3 3-8 0-11l-17-17h15a7 7 0 100-15h-30l-26-26 23-1a7 7 0 004-14l-14-13h35l21 21a7 7 0 0011 0c3-3 3-8 0-11l-11-10h25a7 7 0 100-15zm-89-16l-14 16h-20l15-16h19zm-31-29l1 20-15 14v-21l14-13zm-43 0l14 13v20l-14-14V96zm-30 30h20l14 15h-20l-14-15zm0 43l13-13h21l-14 13H97zm31 33l-1-21 14-15v22l-13 14zm43-1l-15-13v-22l15 14v21zm30-32h-20l-13-14h20l13 14z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297 297"><path d="M295 140a17 17 0 00-30 2h-16l-39-20 14-8 9 7a7 7 0 0010-1c3-3 2-8-1-11l-4-3 11-6 6 2a8 8 0 008-4l9-13a8 8 0 00-7-11h-15c-4 1-6 3-7 6l-2 7-11 6v-4a7 7 0 10-15 2l1 10-14 8 3-43 9-15a16 16 0 009-3 17 17 0 10-22-5l-9 16-35 23V67l10-4c4-2 6-6 4-10-1-4-6-6-10-4l-4 2V38l4-5c3-2 3-6 2-9l-7-13a7 7 0 00-14 0l-7 13c-1 3-1 7 1 9l6 6v11l-5-1c-3-2-8 0-9 4-2 4 0 8 4 10l10 4v16l-37-24-9-15a17 17 0 00-15-26 17 17 0 102 34l10 15 2 42-13-8 2-11a7 7 0 10-15-2l-1 5-11-6-2-7c0-3-3-5-6-5l-16-1a8 8 0 00-7 11l9 14a8 8 0 008 3l7-2 10 6-3 3a7 7 0 109 11l8-6 14 8-40 20H32a17 17 0 00-15-10 17 17 0 00-16 21 17 17 0 0016 12c6 0 11-3 14-8h20l37 18-13 8-9-7a7 7 0 10-10 12l5 3-11 6-7-1c-3-1-6 0-8 3l-9 13a8 8 0 007 11h16c3 0 6-3 6-6l2-7 11-6v4a8 8 0 1015-2l-1-10 13-8-2 43-9 16a17 17 0 1014 7l8-16 36-24v16l-11 5c-4 1-6 5-4 9 1 3 4 5 7 5l2-1 6-2v13l-5 5c-3 2-3 6-2 9l7 14c2 2 4 4 7 4s6-2 7-4l7-14c2-3 1-7-1-9l-5-5v-12l3 1c4 2 9 0 10-4 2-3 0-8-4-9l-9-4v-16l35 24 12 17c-2 2-3 5-3 9 0 9 8 17 17 17a17 17 0 10-2-34l-11-17-3-43 14 8-1 11a7 7 0 1014 2l1-5 11 6 2 7c1 3 3 5 7 6h16a7 7 0 006-11l-8-13c-2-3-6-4-9-4l-7 2-10-6 3-2a7 7 0 10-9-12l-8 6-14-8 37-18h19c3 5 8 8 14 8a17 17 0 0015-25zM189 79l-2 39-30 17v-35l32-21zm-81 0l34 22v35l-31-18-3-39zm-40 70l36-18 30 18-30 17-36-17zm40 69l3-39 31-18v35l-34 22zm81 1l-32-22v-35l30 17 2 40zm5-53l-30-17 30-18 36 18-36 17z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 296 296"><path d="M270 201a15 15 0 00-14 0l-12-6 24-11a7 7 0 00-7-14l-32 16-11-7 23-11a7 7 0 10-7-13l-32 15-24-13v-18l23-14 32 17a7 7 0 0010-4c2-3 1-8-3-10l-23-12 11-6 32 16a7 7 0 0010-3c2-4 1-8-3-10l-23-12 11-7a15 15 0 10-7-13l-11 7 2-26a7 7 0 10-15-1l-3 36-11 6 2-26a7 7 0 10-15-1l-3 36-23 14-15-10V90l29-19a7 7 0 10-8-13l-21 14V59l29-19a7 7 0 10-8-13l-21 14V28c4-3 7-7 7-13a15 15 0 00-30 0c0 6 3 10 8 13v13l-22-15a8 8 0 00-9 13l31 20v13l-22-15a8 8 0 00-9 13l31 20v26l-17 9-22-13-2-35a7 7 0 10-15 1l1 25-11-6-2-36c0-4-4-7-8-7-4 1-7 4-7 8l1 26-11-7a15 15 0 10-8 13l12 7-24 11a7 7 0 107 14l32-16 11 6-23 12a7 7 0 106 13l33-16 23 13v19l-23 13-32-16a7 7 0 10-6 13l22 12-11 7-31-17a7 7 0 10-7 14l23 12-12 6a15 15 0 108 13l11-6-2 26a7 7 0 1015 1l3-36 11-7-2 26a7 7 0 1015 1l2-36 23-13 17 9v26l-31 20a8 8 0 009 12l22-14v13l-31 20a7 7 0 109 12l21-14v13c-5 3-7 8-7 14a15 15 0 1022-13v-13l22 14a7 7 0 0010-2c3-3 2-8-2-10l-30-20v-13l22 14a7 7 0 0010-2c3-3 2-8-2-10l-30-20v-27l16-9 23 13 2 36a7 7 0 1015-1l-2-26 12 6 2 36a7 7 0 1015-1l-2-26 12 7a15 15 0 1022-13z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297 297"><path d="M275 213l-21-12 21-14a8 8 0 00-8-12l-28 17-11-6 11-9a8 8 0 00-9-12l-16 13-25-15v-29l25-15 18 11a7 7 0 0010-2c2-4 1-8-2-10l-11-7 10-6 28 17a7 7 0 0010-3c2-3 1-8-2-10l-21-13 20-11a7 7 0 00-7-13l-21 12-1-26c-1-4-4-7-8-7-4 1-7 4-7 8l1 33-11 7-2-14a7 7 0 10-14 1l2 21-25 14-26-16V78l19-9c4-1 6-6 4-9-2-4-6-6-10-4l-13 5V48l29-15c4-2 6-6 4-10s-7-5-10-3l-23 11V8a7 7 0 10-15 0v23l-21-12c-4-2-8-1-10 3-2 3-1 8 3 10l28 16v12l-11-6c-4-2-8-1-10 3s-1 8 3 10l18 10v29l-25 14-24-13 2-21a7 7 0 10-14-1l-2 14-11-7 1-33a7 7 0 10-15-1l-1 26-21-12a7 7 0 10-7 13l20 11-21 13a8 8 0 008 13l28-17 10 6-11 7a7 7 0 108 12l18-11 24 14v31l-24 14-16-13a8 8 0 00-9 12l11 9-11 6-28-17a8 8 0 00-8 12l21 14-21 12a7 7 0 008 13l20-12v24c0 4 4 7 8 7s7-3 7-7v-32l10-7 1 13a8 8 0 0015 0l-1-21 25-15 25 15v29l-18 10c-4 2-5 6-3 10 1 3 4 4 7 4l3-1 11-6v12l-28 16c-4 2-5 7-3 10 1 3 4 4 7 4l3-1 21-12v24a7 7 0 1015 0v-24l23 11c3 2 8 1 10-3s0-8-4-10l-29-15v-13l13 5c4 2 8 0 10-4 2-3 0-8-4-9l-19-9v-27l27-15 25 14-1 21a8 8 0 0015 0l1-13 10 7v32c0 4 3 7 7 7s8-3 8-7v-24l20 12a7 7 0 0010-3c2-4 1-8-2-10z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 298 298"><path d="M275 213l-17-10 16-7a7 7 0 10-6-14l-27 11-47-27 28-11a8 8 0 000-14l-29-10 48-28 27 11a7 7 0 105-14l-16-6 18-11a7 7 0 00-7-13l-18 10 2-17a7 7 0 10-15-2l-3 29-48 27 5-29c1-3-1-7-3-8-3-2-7-1-9 1l-24 19V48l24-18c3-2 4-7 1-10-2-4-7-4-10-2l-15 11V8a7 7 0 10-15 0v21l-13-10c-3-3-7-2-10 1s-2 8 1 10l22 18v52l-23-19c-2-3-6-3-8-1-3 1-4 4-4 8l5 30-45-27-4-29a7 7 0 10-15 2l3 18-19-11a7 7 0 10-7 13l19 11-16 6a8 8 0 005 14l27-10 45 25-29 11a8 8 0 000 14l29 10-45 27-27-12a7 7 0 10-6 14l16 7-18 11a7 7 0 007 13l19-11-3 16a8 8 0 1015 3l5-29 44-25-5 29c0 3 1 7 4 8 2 2 6 1 8-1l23-19v55l-22 18c-3 2-4 7-1 10s7 4 10 1l13-10v21a7 7 0 1015 0v-21l15 11a8 8 0 0010-2c3-3 2-8-1-10l-24-18v-55l24 19a8 8 0 008 1c3-1 5-4 4-8l-5-30 47 28 5 28a8 8 0 0014-2l-2-17 18 11a7 7 0 0010-3c2-4 1-8-3-10zm-77-65l-21 8-14-8 14-8 21 8zm-25-43l-4 22-14 8v-15l18-15zm-50 0l17 14v16l-13-8-4-22zm-24 43l21-8 13 8-13 8-21-8zm24 43l4-22 13-8v15l-17 15zm50 0l-18-14v-16l14 8 4 22z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 298 298"><path d="M291 142h-12v-11a7 7 0 10-15 0v11h-9v-17a7 7 0 10-15 0v17h-69l-2-3 50-49 12 12a7 7 0 0011 0c3-3 3-8 0-11l-12-12 6-6 8 7a7 7 0 0010 0c3-3 3-7 0-10l-7-8 7-8a8 8 0 00-10-10l-8 7-8-7a8 8 0 00-10 10l7 8-6 6-12-12a8 8 0 00-11 11l12 12-50 50-2-1V58h18a7 7 0 100-15h-18v-9h12a7 7 0 100-15h-12V8a7 7 0 10-15 0v11h-10a7 7 0 100 15h10v9h-16a7 7 0 100 15h16v70l-1 1-50-50 12-12a7 7 0 10-11-11L79 68l-6-6 7-8a8 8 0 00-10-10l-8 7-8-7a8 8 0 00-10 10l7 8-7 8a8 8 0 1010 10l8-7 6 6-12 12a7 7 0 1011 11l12-12 50 50-1 1H57v-16a7 7 0 10-15 0v17h-9v-11a7 7 0 10-15 0v11H8a7 7 0 100 15h10v11a7 7 0 1015 0v-11h9v17a7 7 0 1015 0v-17h71l1 2-50 49-12-12a8 8 0 00-11 11l12 12-6 6-8-7a7 7 0 10-10 10l7 8-7 8a8 8 0 1010 10l8-7 8 7a7 7 0 0010 0c3-3 3-7 0-10l-7-8 6-6 12 12a7 7 0 0011 0c3-3 3-8 0-11l-12-12 49-50 2 2v70h-16a7 7 0 100 15h16v9h-10a7 7 0 100 15h10v11a7 7 0 1015 0v-11h12a7 7 0 100-15h-12v-9h18a7 7 0 100-15h-18v-70l3-1 49 49-12 12a8 8 0 0011 11l12-12 6 6-7 8a8 8 0 0010 10l8-7 8 7a7 7 0 0010 0c3-3 3-7 0-10l-7-8 7-8a8 8 0 00-10-10l-8 7-6-6 12-12a8 8 0 00-11-11l-12 12-49-49 1-3h69v17a7 7 0 1015 0v-17h9v11a7 7 0 1015 0v-11h12a7 7 0 100-14z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 298 298"><path d="M291 143h-23l11-11a7 7 0 10-11-10l-19 19-43-15 20-42 1-1h25a7 7 0 100-14h-10l13-14c3-3 3-8 0-11s-8-3-11 0l-16 16V46a7 7 0 10-15 0v28l-40 19-16-44 19-19c3-2 3-7 0-10s-8-3-10 0l-10 9V8a7 7 0 10-15 0v20l-8-8c-3-3-7-3-10 0s-3 8 0 10l18 19-15 44-42-20V47a7 7 0 10-15 0v12L54 44c-3-3-7-3-10 0s-3 8 0 11l15 15H47a7 7 0 100 15h26l20 41-43 15-19-19a8 8 0 00-11 11l10 9H8a7 7 0 100 15h21l-9 9a8 8 0 1011 10l19-18 43 15-19 41H47a7 7 0 100 15h13l-16 15a8 8 0 006 13c2 0 3 0 5-2l14-14v11a7 7 0 1015 0v-26l42-20 15 43-18 18a7 7 0 005 13l5-2 8-8v21a7 7 0 1015 0v-22l10 9a8 8 0 0010 0c3-3 3-8 0-11l-18-18 15-43 41 20v26a7 7 0 1015 0v-12l15 15a8 8 0 0010 0c3-3 3-8 0-11l-15-15h14a7 7 0 100-15h-28l-19-41 43-16h1l18 19a7 7 0 0011 0c2-3 2-8 0-11l-8-7h20a7 7 0 100-15zm-87 61l-40-19-14 42-15-42-19 9-21 10 19-40-42-14 42-15-19-40 40 19 14-43 15 43 40-19-19 40 42 15-42 14 19 40z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297 297"><path d="M295 140a17 17 0 00-30 2h-16l-39-20 14-8 9 7a7 7 0 0010-1c3-3 2-8-1-11l-4-3 11-6 6 2a8 8 0 008-4l9-13a8 8 0 00-7-11h-15c-4 1-6 3-7 6l-2 7-11 6v-4a7 7 0 10-15 2l1 10-14 8 3-43 9-15a16 16 0 009-3 17 17 0 10-22-5l-9 16-35 23V67l10-4c4-2 6-6 4-10-1-4-6-6-10-4l-4 2V38l4-5c3-2 3-6 2-9l-7-13a7 7 0 00-14 0l-7 13c-1 3-1 7 1 9l6 6v11l-5-1c-3-2-8 0-9 4-2 4 0 8 4 10l10 4v16l-37-24-9-15a17 17 0 00-15-26 17 17 0 102 34l10 15 2 42-13-8 2-11a7 7 0 10-15-2l-1 5-11-6-2-7c0-3-3-5-6-5l-16-1a8 8 0 00-7 11l9 14a8 8 0 008 3l7-2 10 6-3 3a7 7 0 109 11l8-6 14 8-40 20H32a17 17 0 00-15-10 17 17 0 00-16 21 17 17 0 0016 12c6 0 11-3 14-8h20l37 18-13 8-9-7a7 7 0 10-10 12l5 3-11 6-7-1c-3-1-6 0-8 3l-9 13a8 8 0 007 11h16c3 0 6-3 6-6l2-7 11-6v4a8 8 0 1015-2l-1-10 13-8-2 43-9 16a17 17 0 1014 7l8-16 36-24v16l-11 5c-4 1-6 5-4 9 1 3 4 5 7 5l2-1 6-2v13l-5 5c-3 2-3 6-2 9l7 14c2 2 4 4 7 4s6-2 7-4l7-14c2-3 1-7-1-9l-5-5v-12l3 1c4 2 9 0 10-4 2-3 0-8-4-9l-9-4v-16l35 24 12 17c-2 2-3 5-3 9 0 9 8 17 17 17a17 17 0 10-2-34l-11-17-3-43 14 8-1 11a7 7 0 1014 2l1-5 11 6 2 7c1 3 3 5 7 6h16a7 7 0 006-11l-8-13c-2-3-6-4-9-4l-7 2-10-6 3-2a7 7 0 10-9-12l-8 6-14-8 37-18h19c3 5 8 8 14 8a17 17 0 0015-25zM189 79l-2 39-30 17v-35l32-21zm-81 0l34 22v35l-31-18-3-39zm-40 70l36-18 30 18-30 17-36-17zm40 69l3-39 31-18v35l-34 22zm81 1l-32-22v-35l30 17 2 40zm5-53l-30-17 30-18 36 18-36 17z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 298 298"><path d="M268 148l27-27a8 8 0 00-10-11l-30 30h-44l16-16a7 7 0 00-5-13l-25-1 31-31h42a7 7 0 100-14h-38V27a7 7 0 10-15 0v42l-30 30-1-24c0-3-1-5-4-7-3-1-6 0-8 2l-18 16V42l30-29c3-3 3-8 0-11s-8-3-11 0l-27 27-27-27c-2-3-7-3-10 0s-3 8 0 11l30 30v44l-18-17c-2-2-5-3-8-2-2 2-4 4-4 7l-1 24-32-32V25a7 7 0 10-15 0v39H25a7 7 0 100 15h44l31 31-25 1a8 8 0 00-5 12l17 18H43l-30-30a8 8 0 00-11 10l27 27-27 28a8 8 0 1011 10l29-30h44l-16 17a8 8 0 005 13l23 1-32 31H25a7 7 0 100 15h38v38a7 7 0 1015 0v-43l32-32 1 26c0 3 1 5 4 7 3 1 6 0 8-2l17-16v44l-30 30c-3 3-3 8 0 10a8 8 0 0011 0l27-27 27 27a7 7 0 0010 0c3-2 3-7 0-10l-30-30v-45l18 17a8 8 0 009 2c2-2 4-4 4-7l1-24 30 31v42a7 7 0 1015 0v-38h39a7 7 0 100-15h-43l-31-31 25-1a8 8 0 005-12l-17-19h45l30 30a7 7 0 0010 0c3-2 3-7 0-10l-27-27zm-112-41l16-14v21l-14 14-2-1v-20zm-31-14l16 15v19l-2 1-14-14V93zm-10 32l13 13-1 3h-19l-15-16h22zm-2 47H93l14-16h20l1 1-15 15zm27 18l-15 14v-23l13-13 2 2v20zm31 14l-16-15v-19l2-1 15 15-1 20zm11-32l-14-13 2-4h19l15 17h-22zm8-32h-20l-1-1 14-14h21l-14 15z"/></svg>`], {type: 'image/svg+xml'})),
  URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 296 296"><path d="M250 185a7 7 0 00-12 1l-2 4-13-8 19-12a7 7 0 10-8-13l-25 16-19-25 19-26 25 17a7 7 0 0010-2c3-3 2-8-2-10l-19-14 13-8 2 5a7 7 0 0012 1l26-29a7 7 0 00-8-12l-37 8a7 7 0 00-5 11l2 4-13 7-1-23a8 8 0 00-15 1l2 29-32 4-13-29 27-14c3-2 5-6 3-10s-7-5-11-3l-20 11V51h5a7 7 0 006-10L155 5c-1-3-4-5-7-5-4 0-7 2-8 5l-12 36a7 7 0 007 10h5v15l-22-10c-3-2-8-1-10 3-1 4 0 8 4 10l26 13-12 29-31-4 1-29a7 7 0 10-15-1l-1 23-12-7 2-4a8 8 0 00-5-11l-37-8a7 7 0 00-8 13l26 28a7 7 0 0012-1l2-4 13 7-20 14a8 8 0 009 12l24-16 18 25-18 25-25-16a8 8 0 00-8 13l20 12-13 8-2-4a7 7 0 00-12-1l-25 28a8 8 0 007 12l37-7a8 8 0 005-11l-2-5 13-7 1 24a7 7 0 1015-1l-2-29 31-4 12 29-26 13c-4 1-5 6-3 10 1 3 6 5 9 3l22-10v15h-5a7 7 0 00-7 10l12 36c1 3 4 5 8 5 3 0 6-2 7-5l12-36a8 8 0 00-7-10h-5v-15l20 11 4 1c3 0 5-2 7-4 2-4 0-9-3-11l-27-13 13-29 31 4-2 29a8 8 0 0015 1l2-24 13 8-2 4a7 7 0 005 11l37 8a7 7 0 008-4c2-3 2-6-1-9l-25-28z"/></svg>`], {type: 'image/svg+xml'})),
], (url, i) => {
  const image = document.createElement('img');
  image.src = url;

  return new Promise((resolve) => image.addEventListener('load', resolve, {once: true})).then(() => masks[i] = image);
});
```

We'll test that the masks are working by picking one at random and reloading the page several times.

```javascript
async function main() {
  const canvas = document.querySelector('canvas');
  const canvasContext = canvas.getContext('2d');
  const canvasAspectRatio = canvas.width / canvas.height;

  const video = document.createElement('video');
  video.muted = true;
  video.autoplay = true;
  video.loop = true;
  video.playsinline = true;
  video.src = "https://giant.gfycat.com/CheapBitterCaterpillar.webm";

  await Promise.all([...masks, video.play()]);

  const videoAspectRatio = video.videoWidth / video.videoHeight;

  const [frameWidth, frameHeight] =
        canvasAspectRatio <= videoAspectRatio
  ? [canvas.height * videoAspectRatio, canvas.height]
  : [canvas.width, canvas.width / videoAspectRatio];

  const [frameTop, frameLeft] = [(canvas.height - frameHeight) / 2, (canvas.width - frameWidth) / 2];

  const maskImage = masks[Math.floor(Math.random() * masks.length)];

  const maskAspectRatio = maskImage.naturalWidth / maskImage.naturalHeight;

  const [maskWidth, maskHeight] =
        canvasAspectRatio <= maskAspectRatio
  ? [canvas.width, canvas.width / maskAspectRatio]
  : [canvas.height * maskAspectRatio, canvas.height];

  const [maskTop, maskLeft] = [(canvas.height - maskHeight) / 2, (canvas.width - maskWidth) / 2];

  requestAnimationFrame(function frame() {
    canvasContext.globalCompositeOperation = 'destination-over';
    canvasContext.drawImage(maskImage, maskLeft, maskTop, maskWidth, maskHeight);

    canvasContext.globalCompositeOperation = 'source-in';
    canvasContext.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, frameLeft, frameTop, frameWidth, frameHeight);

    requestAnimationFrame(frame);
  });
}
```

### Part 3 Pen

{% codepen https://codepen.io/benjaminblack/pen/ExgZZve %}


---

# Part 4: Use multiple random masks on the same image

Next, instead of a single random snowflake, let's generate lots of flakes of varying size and draw them randomly.

First, let's define a class `Flake` to create a random snowflake. Its constructor will take the `<canvas>` it is drawn on as its only argument. We'll also define two static class variables, `minProportion` and `maxProportion` to determine the min and max size of the snowflake, expressed as a fraction of the minimum dimension (width or height) of the canvas, with some sane defaults.

We'll use a truncated exponential distribution with lambda = 1 to randomize the size of the snowflake, so that small flakes are more likely than big ones. We'll crop the distribution at the 99th percentile and normalize it.

We'll also give the class a `draw` method to draw the flake on the canvas.

```javascript
class Flake {
  static minProportion = 0.01;
  static maxProportion = 0.1;

  constructor(canvas) {
    this.context = canvas.getContext('2d');

    this.image = masks[Math.floor(Math.random() * masks.length)];

    // https://www.wolframalpha.com/input/?i=exponential+distribution
    const lambda = 2, pct = 4.60517 / lambda, r = -1 * Math.log(Math.random()) * 1 / lambda, p = Math.min(pct, r) / pct;
    
    const size = Flake.minProportion + (p * (Flake.maxProportion - Flake.minProportion));
    
    this.width = Math.max(canvas.width, canvas.height) * size;
    this.height = this.width * this.image.naturalWidth / this.image.naturalHeight;
    this.left = Math.floor(Math.random() * canvas.width) - this.width / 2;
    this.top = Math.floor(Math.random() * canvas.height) - this.height / 2;
  }

  draw() {
    this.context.drawImage(this.image, this.left, this.top, this.width, this.height);
  }
}
```

Then, instead of a single mask, we'll create an array of Flakes.

```javascript
  const flakes = Array.from({length: 300}, () => new Flake(canvas));
```

Next, within the `frame` function, loop through flakes and call the `draw` method.

```javascript
  requestAnimationFrame(function frame() {
    canvasContext.globalCompositeOperation = 'destination-over';
    flakes.forEach((flake) => flake.draw());

    // ...
  });
```

### Part 4 Pen

{% codepen https://codepen.io/benjaminblack/pen/MWjJJqG %}


---

# Part 5: Animating masks

Let's animate the snowflakes.

Since we'll be doing things over time, we'll need a way to measure time. We'll use the `performance.now()` API to calculate the elapsed time between each frame.

```javascript
let t0 = performance.now();

requestAnimationFrame(function frame() {
  const t1 = performance.now(), elapsed = t1 - t0;
  t0 = t1;

  // ...
});
```
For simplicity, let's also define a "period" for the amount of time a snowflake falling vertically takes to travel the full length of the canvas. We'll define it as a static variable on the `Flake` class.

```javascript
class Flake {
  static period = 1000; // 1 second period, in ms to match performance.now() units

  // ...
}
```

In the Flake constructor, let's define a velocity vector using the period and the height of the canvas. For initial development, the vector will have only a vertical `y` component and no horizontal `x`. To emulate the perception of depth, smaller snowflakes, since they are perceived to be further away, will have their speed scaled in proportion to their size, with a reasonable minimum.

```javascript
constructor(canvas) {
  // ...

  const speed = Math.max(0.1, p) * canvas.height / period; // (px / ms)
  this.velocity = [0, speed];
}
```

Next, let's define a method to update the position of a Flake, passing it the amount of time that has elapsed. Call it `tick`. When a snowflake moves off of either edge of the canvas, we'll just wrap it around.

```javascript
tick(t = 16.667) {
  const [x, y] = this.velocity;
  let left = this.left + x * t, top = this.top + y * t;

  if (top >= this.canvas.height) {
    top = -this.height;
  }

  if (left >= this.canvas.width) {
    left = -this.width;
  }

  this.left = left;
  this.top = top;
}
```

To make everything move, we'll update the `frame` function to call the `tick` method on each Flake. Now that the masks are moving, we also need to to clear the canvas on each frame.

```javascript
requestAnimationFrame(function frame() {
  const t1 = performance.now(), elapsed = t1 - t0;
  t0 = t1;

  canvasContext.clearRect(0, 0, canvas.width, canvas.height);

  canvasContext.globalCompositeOperation = 'source-over';
  canvasContext.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, frameLeft, frameTop, frameWidth, frameHeight);

  canvasContext.globalCompositeOperation = 'destination-in';

  flakes.forEach((flake) => {
    flake.tick(elapsed);
    flake.draw();
  });

  requestAnimationFrame(frame);
});
```

The rest of the code is just aesthetic tweaks.

Let's move a random percentage of each flake's velocity, from 0% - 33%, into the horizontal component.

```javascript
constructor(canvas) {
  // ...
  const speed = Math.max(0.1, p) * canvas.height / period;
  const xFrac = 0.15 * Math.random();
  this.velocity = [speed * xFrac, speed * (1 - xFrac)];
}
```

Finally, let's add some more randomness into the scene. When a snowflake runs off of the canvas, let's randomize it, except that it will always be positioned just above the top of the canvas (because snowflakes appearing in the middle of the canvas would be weird). To do that, we'll move most of the code out of the constructor into a method that can be called independently.

```javascript
  constructor(canvas) {
    this.canvas = canvas;
    this.context = canvas.getContext('2d');
    this.randomize();
  }

  randomize() {
    this.image = masks[Math.floor(Math.random() * masks.length)];

    // exponential distribution (lambda = 2, percentile = 99%)
    const lambda = 2, pct = 4.60517 / lambda, r = -1 * Math.log(Math.random()) * 1 / lambda, p = Math.min(pct, r) / pct;

    const size = Flake.minProportion + (p * (Flake.maxProportion - Flake.minProportion));

    this.width = Math.min(this.canvas.width, this.canvas.height) * size;
    this.height = this.width * this.image.naturalWidth / this.image.naturalHeight;
    this.left = Math.floor(Math.random() * this.canvas.width) - this.width / 2;
    this.top = -this.height;

    const speed = Math.max(0.1, p) * this.canvas.height / Flake.period;

    const xFrac = 0.333 * Math.random();
    this.velocity = [xFrac * speed, (1 - xFrac) * speed];
  }

  tick(t = 16.667) {
    // ...

    if (top >= this.canvas.height || left >= this.canvas.width) {
      return this.randomize();
    }

    this.left = left;
    this.top = top;      
  }
```

And the very last thing: let's make the canvas cover the entire viewport.

```html
<canvas></canvas>
```

```css
/* ... */

body {
  height: 100%;
}

canvas {
  display: block;
  width: 100%;
  height: 100%;
}
```

```javascript
async function main() {
  const canvas = document.querySelector('canvas');
  const canvasContext = canvas.getContext('2d');

  let canvasAspectRatio;
  
  const resize = () => {
    const { width, height } = canvas.getBoundingClientRect();  
    canvas.width = width;
    canvas.height = height;
    canvasAspectRatio = width / height;
  };
  
  resize();
  
  self.addEventListener('resize', resize);

  // ...
}
```

And we're done!


### Part 5 Pen

{% codepen https://codepen.io/benjaminblack/pen/BaLWPae %}


---

# Conclusion

This is just a proof-of-concept. There are many other possibilities with this technique. The masks could themselves be programmatically animated. Other compositing modes and/or [filters](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter) can be combined and used to remarkable effect. For example, snowflakes closer to the "camera" could have an increasing amount of blur applied to them using a filter.