Convert SVG to PNG inside Web Worker - javascript

I would like to convert an SVG to a PNG inside a Web Worker.
My problem is, that the DOM is not accessible from within the Worker,
so I cannot draw the SVG to a canvas or something like that.

Weeell, you can always manually parse the SVG and build a bitmap from that, but (!) it's a tad more work obviously as you'd have to build a SVG parser as well as a PNG writer, not to mention rasterizing code for lines and two-modes polygon fill incl. anti-aliasing, pattern, matrix, composition, blending and gradient support. Could be a nice weekend project though :)
On a more serious note though: you can only do this with the built-in tools using regular context (none-webworker) or optionally set up a server based service.

You can use thumbo
import Thumbo, { Transfer } from "thumbo";
Thumbo.init().then(async () => {
Thumbo.thumbnail(
Transfer(await (await fetch("/path/to/img.svg")).arrayBuffer()),
Thumbo.ImageFormat.Svg,
20,
20
).then((thumbnailBuffer) => {
document.getElementById("img1").src = URL.createObjectURL(
new Blob([thumbnailBuffer])
);
});
Thumbo.thumbnailFromUrl(
"https://example.com/image.svg",
Thumbo.ImageFormat.Svg,
20,
20
).then((thumbnailBuffer) => {
document.getElementById("img2").src = URL.createObjectURL(
new Blob([thumbnailBuffer])
);
});
});
Under the hood, thumbo uses Rust's tiny_skia & resvg libraries compiled to a Web Assembly module, to render SVG in a web worker and convert it to PNG. See thumbo-core, thumbo
Demo ▶️
Source code
PS: I'm the author of thumbo

Related

Interaction on namespaced SVG

I am trying to insert a SVG diagram on a Web page and add JavaScript interaction.
My issue: the SVG includes elements with namespaces, and when I insert it inline, only the non-namespaced elements are rendered. I have tried direct insertion in the html, innerHTML, and the SVGjs library, all give the same result.
Is there a way to render namespaced SVG on the page and interact with it?
For the record, the SVG files come from Visio and I don't have control on the namespaces. Also, I am using Reactjs if that can help.
Sample script (both methods work but strip out namespaced elements):
fetch (fileURL)
.then(response => response.text())
.then((image) => {
let startOfSvg = image.indexOf('<svg')
startOfSvg = startOfSvg >= 0 ? startOfSvg : 0
// innerHTML method
document.getElementById("map").innerHTML = image.slice(startOfSvg);
// svg.js library
const draw = SVG(image.slice(startOfSvg))
.addTo('#map')
.size('100%', 450);
}
There is a checkbox to export SVG without visio namespace. Other than that, you could take a look at my exporter (it can export pure svg as well, depending on the template you use): https://unmanagedvisio.com/products/svg-publish

PIXI.JS Newbie, looking for advice and troubleshoot of a textureCache, loader issue

ive ventured into the world of PIXI and JS, new programming language for me, liking the way it is right now but i have an issue.
I am a bit confused with the TextureCache and the loader. If you look at part of my code i try to add 3 different images to the screen. Ive been following the 'Get Started section' of the pixi website. I wanted to add their cat image, which i have, the tileset image (all of it)* and then a tile of the tileset image.
The issue is, i create 3 new sprite instances and the tileset image shows the area ive set for the tile(rocket) when i want it to show the whole tileset. Ive loaded in the tileset iin the cahce and the loader.
Why does tile show the cropped image and not the whole image?
Am i using the cache correctly to just store images?
Am i using the resources method properly to locate the image FROM the cache or the loader?
Is there any point of the cache?
my thoughts**
when you use the rectangle method, it destroys the original image and the cropped version is now tileset1 (the name of my image)?
<html>
<body>
<script src="pixi.js"></script>
<script>
//Aliases
let Application = PIXI.Application,
loader = PIXI.loader,
resources = PIXI.loader.resources,
Sprite = PIXI.Sprite,
Rectangle = PIXI.Rectangle,
TextureCache = PIXI.utils.TextureCache;
let app = new PIXI.Application({
width: 1000,
height: 600,
antialias: true,
transparent: false,
resolution: 1
}
);
//Add the canvas that Pixi automatically created for you to the HTML document
document.body.appendChild(app.view);
TextureCache["tileset1.png","images/3.png"];
//load an image and run the `setup` function when it's done
loader.add(["images/3.png","tileset1.png"]).load(setup);
//This `setup` function will run when the image has loaded
function setup() {
let texture = TextureCache["tileset1.png"];
let rectangle = new Rectangle(96,64,32,32);
texture.frame = rectangle;
//Create the cat sprite, use a texture from the loader
let card = new Sprite(resources["images/3.png"].texture);
let tile = new Sprite(resources["tileset1.png"].texture);
let rocket = new Sprite(texture);
card.scale.set(0.06,0.06);
tile.x=400;
tile.y=400;
rocket.x=100;
rocket.y=100;
//Add the cat to the stage
app.stage.addChild(card);
app.stage.addChild(tile);
app.stage.addChild(rocket);
app.renderer.render(app.stage);
}
</script>
</body>
</html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.2.4/pixi.js"></script>
Is there any point of the cache? - this question seems to be most important from your questions :)
Searching around about "pixi.js" and "TextureCache" gives following answers for example:
https://github.com/pixijs/pixi.js/issues/4053
MySecret commented on May 23, 2017:
the docs has no specs about PIXI.utils.TextureCache
...
englercj commented on May 26, 2017
This is intentional, it is not meant as a public-facing API. It is an internal cache used by some texture creation methods.
https://www.html5gamedevs.com/topic/17788-loader-and-texturecache-tutorial-anywhere/
xerver - Pixi.js Moderator:
Here is the loader: https://github.com/englercj/resource-loader
The code is really well documented, and there are examples on the readme. The pixi examples also use the loader a few times: http://pixijs.github.io/examples/
The TextureCache is an internal mechanism, there is rarely any reason you should even know it exists.
https://www.html5gamedevs.com/topic/25575-pixiloaderresources-or-texturecache/
xerver - Pixi.js Moderator:
Dont do this:
let e = PIXI.utils.TextureCache[player.img];
Instead use the resources the loader loaded for you:
let e = PIXI.loader.resources[player.img].texture;
...
TLDR: Usually TextureCache shouldnt be used directly :)
See also:
this explanation: https://www.html5gamedevs.com/topic/9255-pixijs-caching/?tab=comments#comment-55233
http://pixijs.download/release/docs/PIXI.BaseTexture.html#.addToCache
^ example of usage: https://jsfiddle.net/themoonrat/br35x20j/

How do I draw a Javascript-modified SVG object on a HTML5 canvas?

The overall task I'm trying to achieve is to load an SVG image file, modify a color or text somewhere, and then draw it onto an HTML5 canvas (presumably with drawImage(), but any reasonable alternative would be fine).
I followed advice on another StackOverflow question on how to load and modify a SVG file in Javascript, which went like this:
<object class="svgClass" type="image/svg+xml" data="image.svg"></object>
followed in Javascript by
document.querySelector("object.svgClass").
getSVGDocument().getElementById("svgInternalID").setAttribute("fill", "red")
And that works. I now have the modified SVG displaying in my web page.
But I don't want to just display it - I want to draw it as part of an HTML5 canvas update, like this:
ctx.drawImage(myModifiedSVG, img_x, img_y);
If I try storing the result of getSVGDocument() and passing that in as myModifiedSVG, I just get an error message.
How do I make the HTML5 canvas draw call for my modified SVG?
Edit: I can draw an SVG image on an HTML5 canvas already through doing this:
var theSVGImage = new Image();
theSVGImage.src = "image.svg";
ctx.drawImage(theSVGImage, img_x, img_y);
and that's great, but I don't know how to modify text/colors in my loaded SVG image that way! If someone could tell me how to do that modification, then that would also be a solution. I'm not tied to going through the object HTML tag.
For a one shot, you could rebuild a new svg file, load it in an <img> and draw it again on the canvas:
async function doit() {
const ctx = canvas.getContext('2d');
const images = await prepareAssets();
let i = 0;
const size = canvas.width = canvas.height = 500;
canvas.onclick = e => {
i = +!i;
ctx.clearRect(0, 0, size, size);
ctx.drawImage(images[i], 0,0, size, size);
};
canvas.onclick();
return images;
}
async function prepareAssets() {
const svgDoc = await getSVGDOM();
// There is no standard to draw relative sizes in canvas
svgDoc.documentElement.setAttribute('width', '500');
svgDoc.documentElement.setAttribute('height', '500');
// generate the first <img> from current DOM state
const originalImage = loadSVGImage(svgDoc);
// here do your DOM manips
svgDoc.querySelectorAll('[fill="#cc7226"]')
.forEach(el => el.setAttribute('fill', 'lime'));
// generate new <img>
const coloredImage = loadSVGImage(svgDoc);
return Promise.all([originalImage, coloredImage]);
}
function getSVGDOM() {
return fetch('https://upload.wikimedia.org/wikipedia/commons/f/fd/Ghostscript_Tiger.svg')
.then(resp => resp.text())
.then(text => new DOMParser().parseFromString(text, 'image/svg+xml'));
}
function loadSVGImage(svgel) {
// get the markup synchronously
const markup = (new XMLSerializer()).serializeToString(svgel);
const img = new Image();
return new Promise((res, rej) => {
img.onload = e => res(img);
img.onerror = rej;
// convert to a dataURI
img.src= 'data:image/svg+xml,' + encodeURIComponent(markup);
});
}
doit()
.then(_ => console.log('ready: click to switch the image'))
.catch(console.error);
<canvas id="canvas"></canvas>
But if you are going to do it with a lot of frames, and expect it to animate...
You will have to convert your svg into Canvas drawing operations.
The method above is asynchronous, so you cannot reliably generate new images on the fly and get it ready to be drawn in a single frame. You need to store a few of these ahead of time, but since how long it will take to load the image is completely random (at least it should be) this might be a real programming nightmare.
Add to that the overhead the browser will have in loading a whole new SVG document every frame (yes, browsers do load the SVG document even when loaded inside an <img>), then paint it on the canvas, and finally remove it from the memory which will get filled in no time, you won't have a much free CPU to do anything else.
So the best here is probably to parse your SVG and to convert it to CanvasRenderingContext2D drawing operations => Draw it yourself.
This is achievable, moreover now that we can pass d attributes directly into Path2D object constructor, and that most of SVG objects have correspondence in the Canvas2D API (we even can use SVG filters), but that's still a lot of work.
So you may want to look at libraries that do that. I'm not an expert in libraries myself, and I can't recommend any, but I know that canvg does that since a very long time, I just don't know if they do expose their js objects in a reusable way. I know that Fabric.js does, but it also comes with a lot of other features that you may not need.
The choice is yours.

How to get time of page's first paint

While it is easy enough to get firstPaint times from dev tools, is there a way to get the timing from JS?
Yes, this is part of the paint timing API.
You probably want the timing for first-contentful-paint, which you can get using:
const paintTimings = performance.getEntriesByType('paint');
const fmp = paintTimings.find(({ name }) => name === "first-contentful-paint");
enter code here
console.log(`First contentful paint at ${fmp.startTime}ms`);
Recently new browser APIs like PerformanceObserver and PerformancePaintTiming have made it easier to retrieve First Contentful Paint (FCP) by Javascript.
I made a JavaScript library called Perfume.js which works with few lines of code
const perfume = new Perfume({
firstContentfulPaint: true
});
// ⚡️ Perfume.js: First Contentful Paint 2029.00 ms
I realize you asked about First Paint (FP) but would suggest using First Contentful Paint (FCP) instead.
The primary difference between the two metrics is FP marks the point
when the browser renders anything that is visually different from what
was on the screen prior to navigation. By contrast, FCP is the point
when the browser renders the first bit of content from the DOM, which
may be text, an image, SVG, or even a canvas element.
if(typeof(PerformanceObserver)!=='undefined'){ //if browser is supporting
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.entryType);
console.log(entry.startTime);
console.log(entry.duration);
}
});
observer.observe({entryTypes: ['paint']});
}
this will help you just paste this code in starting of your js app before everything else.

P5js SVG Export

I have a P5js sketch which creates a font that users can type anything they want.
I want to allow the user to download an svg/pdf (vector) version of their result.
As of now I succeeded to have them download a .jpg version using the save order and saving a shot of my screen.
Any ideas?
That's an old thread, but maybe my solution will be useful to someone. To export vector graphics in P5js to SVG file, first I use SVG renderer from p5.svg.js library - this will put svg element directly into HTML document. Exporting is to extract the content of that element (by outerHTML property) and save it to a file (like in this post).
So, for example, your "Save as SVG" button callback function may look like this:
function downloadSvg()
{
let svgElement = document.getElementsByTagName('svg')[0];
let svg = svgElement.outerHTML;
let file = new Blob([svg], { type: 'plain/text' });
let a = document.createElement("a"), url = URL.createObjectURL(file);
a.href = url;
a.download = 'exported.svg';
document.body.appendChild(a);
a.click();
setTimeout(function()
{
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
From googling "p5.js svg", there doesn't seem to be a built-in way to work with SVG images in p5.js.
However, that search returns several promising results:
Here is a GitHub issue with a discussion about working with SVGs in p5.js.
Here is a project that attempts to add SVG support to p5.js:
The main goal of p5.SVG is to provide a SVG runtime for p5.js, so that we can draw using p5's powerful API in <svg>, save things to svg file and manipulating existing SVG file without rasterization.
Another discussion that links to two more SVG libraries.
The p5.SVG library sounds especially promising. I suggest you try something out and post an MCVE if you get stuck.
A new package called canvas-sketch seems to solve this issue. They have a wealth of examples for p5.js as well.
const canvasSketch = require('canvas-sketch')
const p5 = require('p5')
const settings = {
p5: { p5 },
// Turn on a render loop
animate: true,
}
canvasSketch(() => {
// Return a renderer, which is like p5.js 'draw' function
return ({ p5, time, width, height }) => {
// Draw with p5.js things
p5.background(0)
p5.fill(255)
p5.noStroke()
const anim = p5.sin(time - p5.PI / 2) * 0.5 + 0.5
p5.rect(0, 0, width * anim, height)
};
}, settings)
If you use p5 globally, there's also an example for that called animated-p5.js

Categories