YCM Jason

Do you know about these SVG techniques?

I recently discovered 3 really exciting SVG Techniques:

  1. Convert SVG to image with canvas
  2. HiDPI Canvas
  3. Prefetching SVG: Verrrrrryyyy cool

I can't wait to tell you about them!

# Background Story

So I created Faviator a few months back and so far I have received some really good feed back. Check it out and star it if you haven't already done so.

Faviator relied on a library called convert-svg which basically takes a screenshot of the SVG with puppeteer (headless chrome). The library was chosen because of its intuitive API and the fact that it uses puppeteer, which guarantees that the image output would look exactly the same as what we see on Chrome.

However, there are two main issues this library:

  1. Image quality is not excellent; blurry image is generated
  2. CSS @import() or url() are not always being loaded when the screenshot is taken

Don't get me wrong. convert-svg is still an amazing project!

The second issue is very crucial for Faviator which fetches font files from Google Fonts. I did find out how to fix it and submitted a PR, but the owner seems to be too busy to spend time on the project.

So I decided to create my own: @ycm.jason/svg-to-img; aiming to solve these problems. While working on this, I have discovered some really cool techniques to be used with SVG, so I thought I could make a blog post about my discoveries.

# Convert SVG to image with canvas

As I mentioned, one way to convert SVG to image is by doing a screenshot with puppeteer. It is actually a pretty nice approach as we won't have to worry about any error that could occur during the rendering of the SVG.

Another approach is with HTML5 canvas. We can draw an <img> on a canvas easily with the drawImage method. As a combo, canvas also provides a toDataURL method which exports the drawing to either PNG or JPEG format.

Okay.. So what?

This mean, we can:

  1. Point an <img> to a SVG
  2. Draw the <img> on a <canvas>
  3. Export the <canvas> as a PNG or JPEG

Here is a quick demo:

const img = document.createElement('img');
img.src = 'some/path/to/the/awesome.svg';
img.onload = () => {
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');
  // draw img to (0, 0) on the canvas
  context.drawImage(img, 0, 0);
  // export the PNG or JPEG
  const pngDataURL = canvas.toDataURL('image/png');
  const jpegDataURL = canvas.toDataURL('image/jpeg');
  // ... do something with them ...
};

Notice all this happens in the browser, which enable @ycm.jason/svg-to-img to support both the browser and Node.js (with puppeteer).

# HiDPI Canvas

HiDPI Canvas is a technique introduced by Paul Lewis. It addresses the problem with the High DPI devices and the drawing of canvas. Please see his article for detailed explanation.

Summary:

  1. Canvas drawing are drawn with 2x the pixels in High DPI devices to keep the width and height
  2. This is basically upscaling the image which leads to blurry image
  3. To solve this, we can draw the canvas 2x the intended size and use CSS to shrink it back to intended size.

Quick example (intended to draw 200x500):

<canvas width="400" height="1000" style="width: 200; height: 500">
</canvas>

This technique enable me to convert SVG to sharp images on high DPI screens. However, it requires some manual resizing of the image. The current implementation uses jimp but I intend to write a smaller one just for this purpose. (Since now I include the whole freaking jimp inside the bundle. This is sinful...)

You can see how different they are: Original SVG https://svgshare.com/i/7Sp.svg

(the image showing is not an SVG... The image host convert it to png...)

Without HiDPI technique

With HiDPI technique

Can you see the difference? If you focus on tips of the "F", you will notice how the last one matches the original SVG more.

Although this definitely improves the image detail, it still appears to bit a little bit blurry. This could be caused by the resizing of the image. I am not an expert in image processing, would be nice if you can tell me some useful techniques that I could use here.

# Prefetching SVG

CSS could be embedded in SVG to control the styles. With the introduction of CSS3 @import, we could now include css inside css definition! How amazing!

However, if you are displaying your SVG in <img>, you might find out that the styles are not imported. The browser (or just Chrome) seems to ignore any external resources if SVG is used in <img>.

I invented a technique called Prefetching SVG which can solve the above problem and make your SVG looks the same even when you are offline!

The idea is to replace @import with the content that it is importing. Replace all url() with a data url. I have created a library to do this: prefetch-svg.

Without prefetching

Since you might have the font-family installed locally, I explicitly removed the @import to demonstrate.

With prefetching

# That's it

And that's it. Here is my little sharing about SVGs. Tell me what you think! Have I missed anything?