I recently discovered 3 really exciting SVG Techniques:
- Convert SVG to image with canvas
- HiDPI Canvas
- 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. 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:
- Image quality is not excellent; blurry image is generated
- CSS
@import()
orurl()
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:
- Point an
<img>
to a SVG - Draw the
<img>
on a<canvas>
- 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:
- Canvas drawing are drawn with 2x the pixels in High DPI devices to keep the width and height
- This is basically upscaling the image which leads to blurry image
- 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...)
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?
Top comments (4)
Ingenieus technique.
My question is what is the use case for wanting to convert an svg to a pixel image. I can't think of anything.
You cannot use SVG as a favicon yet. So... there is at least this one case where it is useful!
faviator.xyz/
Hi, exported SVG doesn't work. It works on web but when I try to open it in the Illustrator, it appears error and represents the standard fonts.
imgur.com/bNBXkgr
imgur.com/hGfdMR1
About converting svg to png, I made a little tool a fair while ago at math2001.github.io/svg2png.
Nice tricks :D