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
Related
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/
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.
I'm looking for library or code snippet which will help me to convert HTML DOM node element to image/png file.
I tried to use html2canvas library but it does not render svg nodes, and in my current project i have a lot of them. Also i tried to use canvg library to convert all SVG elements on page to Canvas elements, but canvg always threw error on render step:
Uncaught (in promise) TypeError: Cannot read property 'ImageClass' of
undefined
Code snippet that is used to conver SVGs to Canvas:
export const svgToCanvas = () => {
const print = document.getElementsByClassName('print-page')[0]
const svgElements = print.getElementsByTagName('svg')
_.each(svgElements, e => {
let xml
const canvas = document.createElement('canvas')
canvas.className = 'screenShotTempCanvas'
xml = (new window.XMLSerializer()).serializeToString(e)
xml = xml.replace(/xmlns=http:\/\/www\.w3\.org\/2000\/svg/, '')
canvg(canvas, xml)
e.parentNode.insertBefore(canvas, e.nextSibling)
e.classList.add('tempHide')
e.style.display = 'none'
})
const temps = document.getElementsByClassName('.screenShotTempCanvas')
_.each(temps, e => {
e.remove()
})
const svgs = document.getElementsByClassName('tempHide')
_.each(svgs, e => {
if (e) {
e.style.display = 'block'
e.classList.remove('tempHide')
}
})
}
Error throws on this line in canvg code:
if (nodeEnv) {
if (!s || s === '') {
return;
}
ImageClass = opts['ImageClass']; //<= error throws here
CanvasClass = target.constructor;
svg.loadXml(target.getContext('2d'), s);
return;
}
I also tried to convert node to PDF format using jsPDF and html-pdf libraries, but in this case all styles are disappeared from node, and i need them not to be lost.
Can anyone provide me with appropriate approach how to convert HTML DOM node (which is rich on SVG elements) to image ?
This seems to be the result of seriously bad testing and documenting by canvg authors. The line the error is thrown at was introduced as part of a pull request that supposedly added support for executing canvg in a node environment. But it seems it was only tested to work if it was used as a dependency in node-svg2img.
If you look at that source, you will find the following (excerpt):
var canvg = require('canvg'),
Canvas = require('canvas');
function convert(svgContent) {
var canvas = new Canvas();
canvg(canvas, svgContent, { ignoreMouse: true, ignoreAnimation: true, ImageClass: Canvas.Image });
return canvas;
}
As you can see, canvg is called with an option ImageClass that has never been documented, and whose content is dependent on a module canvas that is never stated as a dependency for canvg. (And that is not a lightweight one, it is the complete <canvas> implementation.)
Now you never stated that you were doing all this in node, but I will assume you do, otherwise I would not understand why the line causing the error got called at all.
It seems a successfull call to document.createElement('canvas') does not indicate you have the canvas module installed. This note in the jsdom doc tells you what needs to be done.
From there, calling canvg in your code with
import Image from 'canvas';
canvg(canvas, xml, {ImageClass: Image});
should get you runing.
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
So, in Sketch, you can mark a layer/group as exportable.
And then the layer/group can be exported as .png/.svg/.pdf etc. I was trying to make a Sketch Plugin recently, where I need to mark a layer/group as exportable from code. A layer in code is represented using MSLayer and group is MSLayerGroup. The sketch documentation is not mature enough yet, so I used ClassDump to extract all the headers that has been used in the app. I have been looking for a method that might seem to do my job, but it has been days and am still out of luck. Can anybody please help me out in this regard?
Sketch supports slice and export to image. You can use - (void)saveArtboardOrSlice:(id)arg1 toFile:(id)arg2;
method of MSDocument.
This is almost how to do it.
var loopLayerChildren = [[layerToExport children] objectEnumerator],
rect = [MSSliceTrimming trimmedRectForSlice:layer],
useSliceLayer = false,
exportFilePath,
slice;
// Check for MSSliceLayer and overwrite the rect if present
while (layerChild = [loopLayerChildren nextObject]) {
if ([layerChild class] == 'MSSliceLayer') {
rect = [MSSliceTrimming trimmedRectForSlice:layerChild];
useSliceLayer = true;
}
}
slice = [MSExportRequest requestWithRect:rect scale:1];
if (!useSliceLayer) {
slice.shouldTrim = true;
}
// export to image file
[(this.document) saveArtboardOrSlice: slice toFile:exportFilePath];
Reference from #GeertWill's sketch-to-xcode-assets-catalog plugin.