Can't properly import SVGs to canvas - javascript

I'm having problems to import SVGs to the canvas and using setZoom() with the FabricJS. I'm using version "2.0.0.rc4".
I've been trying to import them using two methods, but each one has different problems:
1- loadSVGFromURL
fabric.loadSVGFromURL(src, function(objects, options) {
let loadedObjects = new fabric.Group(group);
var obj = fabric.util.groupSVGElements(objects, options);
canvas.add(obj).renderAll();
});
With this method some SVGs load incorrectly on the canvas, but the zoom works perfectly.
loadSVGFromURL loads the SVG incorrectly
2- new fabric.Image
const image = new Image();
image.crossOrigin = "Anonymous";
image.src = src;
let imageObject;
image.onload = () => {
imageObject = new fabric.Image(image, {
scaleY: 1,
scaleX: 1,
cropX: 0,
cropY: 0,
lockUniScaling: true,
crossOrigin: 'Anonymous'
});
}
With this method every SVG is correctly imported, but when I try to use the zoom in my app, the shapes of the SVG inside the viewbox (container) adjust their size independently, like it's being masked, cropped or clipped. I guess is something related to the preserveAspectRatio property, but I can't make it work.
This is the method I'm using for setting the zoom. The method works correctly for the canvas and other objects, except the SVGs imported with the method previously described.
setCanvasZoom(value) {
// value is from 10 to 500.
// the zoomFactor will result in an integer from 0.1 to 5
let zoomFactor = parseInt(value, 10) / 100;
this.canvas.setZoom(zoomFactor);
this.canvas.setWidth(this.templateDimensions.width * zoomFactor);
this.canvas.setHeight(this.templateDimensions.height * zoomFactor);
this.canvas.renderAll();
}
The shapes are adjusted independently to the container
Am I doing something wrong using the first method to import SVGs? I tried optimizing the SVGs with svgo and also edit them in Illustrator but with no success (in fabricjs/kitchensink loads incorrectly as well).
Does exists a way to lock the SVGs inside the container, using the second method? Should I use another method to set the zoom?
I'd really appreciate any help with these issues.

there was indeed a bug in circle and ellipse parsing at the time of writing this question. circles were inheriting width and height from the svg document overriding radius value and displacing them.
The bug has been fixed here,
https://github.com/kangax/fabric.js/pull/4637/files#diff-35fc8e842fb0e1e1953d9ba21a292160R189
the bug has been introduced between rc3 and rc4 but now you can import the path normally without preprocessing the SVG, provided you download last version.

The problem was with the SVGs files, the circle tags were imported incorrectly to the FabricJS canvas, placing the objects in other places, even outside the original viewBox.
I tested both versions of the SVG in FabricJS Kitchensink:
Screenshot with the differences between SVG imported with <circle> tags and <circle> tags converted to <paths>
I used svgo to process all the SVGs files with the option of convertShapeToPath included and the parameter convertArcs set as true.
{
"plugins": [
{"convertShapeToPath": {"convertArcs": true}},
]
}
It's also possible to convert the primitive shapes to paths in Illustrator selecting the object and making a compound path of it (menu Object > Compound path > Create or just cmd ⌘ + 8).
I couldn't find an issue on GitHub or Stack Overflow about this problem, so I really hope this solution could help others facing the same problem.
Thank you very much to the developers and maintainers of FabricJS for the amazing work!
Gists of SVGs files:
SVG with <circle> tags
SVG with <circle> tags converted to <path>

Related

objects in canvas aren't loading with the same size - fabric.js loadFromJSON

https://jsfiddle.net/Wadsack/ovcx1rtj/38
var canvas = new fabric.Canvas('canvas_1');
var canvas2 = new fabric.Canvas('canvas_2');
var imgObj = new Image();
imgObj.src = "https://gtaprinting.ca/wp-content/uploads/2021/05/blank-t-shirt-front-grey.png";
imgObj.onload = () => {
var image = new fabric.Image(imgObj);
image.set({
left: canvas.getWidth()/2,
top: canvas.getHeight()/2,
originX: 'center',
originY: 'center',
centeredScaling: true,
}).scaleToWidth(canvas.getWidth()/2);
canvas.add(image);
canvas.renderAll();
canvas2.loadFromJSON(JSON.stringify(canvas));
canvas2.renderAll();
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.2.0/fabric.min.js"></script>
<canvas id="canvas_1"></canvas>
<canvas id="canvas_2"></canvas>
When creating 2 duplicate canvases and using toJSON to export the first canvas and loadFromJSON to populate the second canvas, results in the objects in the second canvas being smaller than the first.
I'm using fabricjs 4.2.0
Does anyone know why this is happening or a solution to the problem? I have tried everything but to make matters worse when combining png and jpeg files in the same canvas, they are both off in size by different amounts. ie the png is 10% smaller and the jpeg is 10% smaller
Internet you are my only hope
http://fabricjs.com/fabric-gotchas
Wrong position after reloading a JSON object - NUM_FRACTION_DIGITS
Fabric can serialize and deserialize objects in a plain object format.
When dealing with serialization, floats can be a problem and give long
strings with an unnecessary quantity of decimals. This blows up the
string size. To reduce that, there is a constant defined on the Object
called NUM_FRACTION_DIGITS, historically set to 2. That means that a
top value of 3.454534413123 is saved as 3.45, same for scale,
width, height. This is mostly fine unless you are dealing without
situation where precision matter. To make an example, a very large
image, can be scaled down to a small size using a scale of 0.0151.
In this case a serialization would save it as 0.02 changing
meaningully the scale. If you are facing such situations, in your
project set the constant higher: fabric.Object.NUM_FRACTION_DIGITS = 8 to have 8 decimals on properties. This affects SVG export too.
your image is more than 4000px large, you fall into this issue
corrected fiddle with the "num fraction digits" set to 8 : https://jsfiddle.net/xrfa5dzc/

Opencv remove unwanted part of an image

I am trying to detect some meter reading of an analogue meter. I am currently using the amazon recognition service to extract readings from a meter in a react-native app. The process did not work very well so as part of trying to fix this. I implemented a cropping functionality in the app so we send only relevant part of the image to the service. I run into another problem. The analogue separators on the meter are interspersed such that they are read as ones.
uncroppped meter image
uncroppped meter image
cropped image from the mobile app
cropped image from the mobile app
What I have tried. I created a simple server application to try to remove these lines before we send the image to rekognito
Converted the image to greyscale
Applied Gaussian blur to remove some of the noise.
Applied the [canny algortihm (https://en.wikipedia.org/wiki/Canny_edge_detector) to detect the edges.
using opencv for node
const { img } = req.params; // Mat
const grayWithGaussianBlur = img
.cvtColor(cv.COLOR_BGR2GRAY)
.gaussianBlur(new cv.Size(5, 5), 0, 0, cv.BORDER_DEFAULT)
.canny(30, 150);
The result look like this.
result
The output is as I expect. I have been trying to figure out how to remove the interspersed edges leaving the clearly defined edge.
I filtered the contours only leaving contours that meet specific criteria. Like area greater than a certain threshold,
const contours = grayWithGaussianBlur.copy().findContours(cv.RETR_TREE, cv.CHAIN_APPROX_NONE);
const viable = contours.filter(contour => {
const { width,height } = contour.boundingRect();
return width > 5 && width <= height; // example criteria
});
const newImage = new cv.Mat(grayWithGaussianBlur.rows, grayWithGaussianBlur.cols, 0);
newImage.drawContours(viable, new cv.Vec3(255, 255, 255), -1);
Can't get this to work.
My understanding of image processing concepts are very vague and I am unsure of this is a good way to fix this problem. I also don't know much about what I am doing :).
Sorry, I don't have enough reputations to embed the images directly.
Can anyone help or suggest a better approach to removing the lines. Thanks in advance.

how to access image data in Elm?

How do we get the Pixel data from images in Elm?
Here in JavaScript, is code to get the color of a set of pixels in a figure (taken from here)
var image = new Image;
image.src = "starry-night.jpg";
var canvas = d3.select("body").append("canvas");
var context = canvas.node().getContext("2d");
context.drawImage(image, 0, 0);
// beware variable name "image" got used twice
image = context.getImageData(0, 0, width, height);
var x = Math.random()*width,
y = Math.random()*height,
i = (y * width + x) << 2;
pixelColor = d3.rgb(image.data[i + 0], image.data[i + 1], image.data[i + 2]) + "";
The code loads an image to a <canvas> element, then extracts the color of a pixel from the canvas using image.getImageData().
Can we interface the image.data object in Elm? Right now I don't think it's possible...
Right now Collage types are list of forms...
HTML is also a module that can put imags in the DOM.
SVG allows for some simple global image transformations but nothing at the pixel level
Elm has the Virtual Dom. In fact of problems like this, might be addressed in virtual-dom which is lower level so we are not encouraged to do this directly.
However, Elm makes a clear distinction between Collage elements and SVG elements, with no clear interface to the getImageData() function.
Do I write my own with Elm's new interOp feature?
Does a way already exist in Elm? Or a new one has to be written?
JavaScript
The << operator is called Left Shift
As suggested by #SimonH, use a port to JS until Elm provides a first-hand way to do so (if it ever does). The same approach would apply to anything you can't yet do in Elm.
I'm just answering as an answer rather than a comment for the sake of others who come here.

animating path segments of an imported svg in paper.js

I am trying to use paper.js to animate svgs I designed in Illustrator. Specifically what I am trying to do is import the svg, and then expand and contract the points on the path. Here is an example of moving paths in paper.js: http://paperjs.org/tutorials/paths/working-with-path-items/
What I want eventually is the path to expand based off the width of another object. It will expand if the object is touching it, and contract back to normal if the object is no longer touching it.
I know that to import the svg I use:
project.importSVG(document.getElementById('svg')[, options.expandShapes: true ]);
The option.expandShapes: true, from what I understand expands the shapes to path items.
How do I access the paths of the svg, in order to expand and contract them?
Here is my SVG path :
path fill="#FFD495" d="M456.5,1396.3c-21-31.5-47.7-90.4-45.3-135.6c3.3-62.9,21.3-997.5,15.1-1014.5
c-12.6-34.9-7.8-113.1-100.3-111.6c-107.1,1.8-132.7,6.6-159.4,6.6c-47.9,0-23-91.8-23-91.8s39.6-7.6,107.8-7.6s136.8-22.2,205,30.8
s67.6,52.5,75.2,97.9c5.7,34.3,30.3,1039.4,30.3,1075.1c0,53-22.7,98.4,7.6,98.4c0,0,103.5,134-104,58.3"
Using project.importSVG has actually inserted your SVG paths as Path items in your project.
These are stored in an array as children of the project, accessible by using project.activeLayer.children.
If you do console.log(project.activeLayer.children), you should be able to inspect them in your console.
So now you can access the Path's themselves, second step is to access the Segments of those paths, if you wish to do so.
Segments, are children of Paths so they can be acccessed by Path.segments.
You should read about Project Hierarchy and Path Items
You can't treat it as a path after you import the SVG. But here is a more simple way to achieve your goal.
Just create your SVG as a Path directly in Paper.js via pathData using SVG path syntax.
like this:
pathData = 'M456.5,1396.3c-21-31.5-47.7-90.4-45.3-135.6c3.3-62.9,21.3-997.5,15.1-1014.5c-12.6-34.9-7.8-113.1-100.3-111.6c-107.1,1.8-132.7,6.6-159.4,6.6c-47.9,0-23-91.8-23-91.8s39.6-7.6,107.8-7.6s136.8-22.2,205,30.8s67.6,52.5,75.2,97.9c5.7,34.3,30.3,1039.4,30.3,1075.1c0,53-22.7,98.4,7.6,98.4c0,0,103.5,134-104,58.3';
var path = new Path(pathData);
path.strokeColor = 'red';
path.strokeWidth = 10;
see the result in Paper.js Sketch
And then you can animate this path.

html5 canvas - Saving paths or clip areas to reuse

I'm currently implementing a 2d deformable terrain effect in a game I'm working on and its going alright but I can envision it becoming a performance hog very fast as I start to add more layers to the effect.
Now what I'm looking for is a way to possibly save a path, or clipping mask or similar instead of having to store each point of the path in the terrain that i need to draw through each frame. And as I add more layers I will have to iterate over the path more and more which could contain thousands of points.
Some very simple code to demonstrate what I'm currently doing
for (var i = 0; i < aMousePoints.length; i++)
{
cRenderContext.save();
cRenderContext.beginPath();
var cMousePoint = aMousePoints[i];
cRenderContext.arc(cMousePoint.x, cMousePoint.y, 30, 0, 2 * Math.PI, false);
cRenderContext.clip();
cRenderContext.drawImage(cImg, 0, 0);
cRenderContext.closePath();
cRenderContext.restore();
}
Basically I'm after an effecient way to draw my clipping mask for my image over and over each frame
Notice how your clipping region stays exactly the same except for its x/y location. This is a big plus.
The clipping region is one of the things that is saved and restored with context.save() and context.restore() so it is possible to save it that way (in other words defining it only once). When you want to place it, you will use ctx.translate() instead of arc's x,y.
But it is probably more efficient to do it a second way:
Have an in-memory canvas (never added to the DOM or shown on the page) that is solely for containing the clipping region and is the size of the clipping region
Apply the clipping region to this in-memory canvas, and then draw the image onto this canvas.
Then use drawImage with the in-memory canvas onto your game context. In other words: cRenderContext.drawImage(in-memory-canvas, x, y); where x and y are the appropriate location.
So this way the clipping region always stays in the same place and is only ever drawn once. The image is moved on the clipping-canvas and then drawn to look correct, and then the in-memory canvas is drawn to your main canvas. It should be much faster that way, as calls to drawImage are far faster than creating and drawing paths.
As a separate performance consideration, don't call save and restore unless you have to. They do take time and they are unnecessary in your loop above.
If your code is open-source, let me know and I'll take a look at it performance-wise in general if you want.
Why not have one canvas for the foreground and one canvas for the background? Like the following demo
Foreground/Background Demo (I may of went a little overboard making the demo :? I love messing with JS/canvas.
But basically the foreground canvas is transparent besides the content, so it acts like a mask over the background canvas.
It looks like it is now possible with a new path2D object.
The new Path2D API (available from Firefox 31+) lets you store paths, which simplifies your canvas drawing code and makes it run faster. The constructor provides three ways to create a Path2D object:
new Path2D(); // empty path object
new Path2D(path); // copy from another path
new Path2D(d); // path from from SVG path data
The third version, which takes SVG path data to construct, is especially handy. You can now re-use your SVG paths to draw the same shapes directly on a canvas as well:
var p = new Path2D("M10 10 h 80 v 80 h -80 Z");
Information is taken from Mozilla official site.

Categories

Resources