Get the canvas object while using fabric js - javascript

I'm using Fabric.js and I've created a fabric canvas object at one place.
var x = new fabric.Canvas("mycanvas");
Now at another place, I want to access this object where 'x' won't be available. So how can I get the same fabric canvas object.
I don't wanna change the scope of x or pass x as arg.
Also, how to get the toDataURL from the fabric canvas object?

Assuming that mycanvas is the ID of a Canvas element, you could store the reference to the fabric object on the Canvas element itself:
var x = new fabric.Canvas("mycanvas");
document.getElementById("mycanvas").fabric = x;
You can then retrieve that object any time you want with:
var y = document.getElementById("mycanvas").fabric;
var url = y.toDataURL("png", 1);

Related

How to give a unique "id" for each canvas?

Canvas is new to me - so I need help.
I have a function that draws data retrieved using getImageData () - on an existing canvas or newly created.
I need to make it possible for a newly created canvas to give it a unique id. However, when I try the following - the canvas is created but the id is undefined.
function drawImageData(img, t1, t2, cc, scale, targetCanvas, userId) {
var canvas = targetCanvas || document.createElement('canvas');
if(!targetCanvas){
canvas.setAttribute('id', userId);
// the rest of the code
}
How to properly assign userId - to "id" canvas?
Your code seems to be working fine.
jsfiddle.net/qsekzt9x/
#gurvinder372 is right about the if condition in his answer below

Limit camera rotation on the Y axis

I am using JSModeler to display OBJ files. It internally uses THREE.JS and creates a PerspectiveCamera. What I need is to limit the movement of the camera on the Y axis so not to go underneath the object. I know how to do this with THREE.OrbitControls but this doesn't work with JSModeler. Is there a way to directly control the camera movement? Thanks.
jsmodeler creates its own set of controls which it stores in JSM.navigation object.
viewer = new JSM.ThreeViewer ();
camera_object = viewer.navigation.camera
The drawLoop function takes in values in this viewer.navigation.camera and renders them on the canvas. So, suppose if you change the values in the viewer.navigation.camera, and call drawCallback(), the changes would get rendered.
viewer.navigation.camera.eye.x = 1; // Any value you want
viewer.navigation.camera.eye.y = 1; // Any value you want
viewer.navigation.camera.eye.z = 1; // Any value you want
viewer.navigation.drawCallback();

Dynamically adding new fabric canvases

I am working on a project that requires creating multiple canvas elements (separate bars each representing one distinct gradient). I would like to do this dynamically with fabric.js, eg:
function add_gradient(color_stops){
// Add a new rectangular canvas with fill representing specified gradient
var grad_box = document.getElementById("divWithCanvases");
var newCanvas = document.createElement("canvas");
grad_box.appendChild(newCanvas);
var gradCanvas = fabric.Canvas(newCanvas, {width: 500, height:50});
var ctx = gradCanvas.getContext('2d');
// Do stuff to canvas...
}
However, the call to fabric.Canvas fails with an error "this.initialize is undefined". (fabric.js line 1627, version 1.4.13)
How can I either:
Generate a new fabric canvas based on an HTML element (instead of the string id), or,
Append an auto-generated new canvas element to the DOM? (the form fabric.Canvas() without arguments will make... something... but it can't be used with appendChild)
According to the fabric.js documentation, the fabric.Canvas constructor accepts either an HTMLElement, or a string element id. I can only make it work with the string.
The function fabric.Canvas is a constructor, you have to call it with the new operator:
var gradCanvas = new fabric.Canvas(newCanvas, {width: 500, height:50});
I was getting the above error because of <div id="canvas"> </div>
Make sure your html element should be canvas
<canvas id="canvas"> </canvas>
When we want to add canvas dynamically and want to call canvas fabric API then we need to give a different name like this..
var html_canvas = document.createElement('canvas');
html_canvas.id = "CursorLayer";
var canvas = new fabric.Canvas(html_canvas, canvasConfigOptions);
// canvasConfigOptions is optional
Now we can perform all fabric operations on this canvas

Create 2d context *without* canvas

I am currently looking for a way to create a canvas 2d rendering context without actually having a canvas element on the page. I could dynamically create a canvas element and hide it, but then again I don't want to show the image directly to the user anytime, so there's no point of actually having a canvas element in the page. So I'm basicly looking for something that is similar to
var image = new Image( );
but only for canvas 2d rendering context (pseudo code)
var context = new 2dContext( );
Is there functionality like this? I wasn't able to find anything like it. Calling
var context = new CanvasRenderingContext2D( );
which is the name of the rendering context interface by HTML5 spec just gives me awkward errors in Firefox:
uncaught exception: [Exception... "Cannot convert WrappedNative to function" nsresult: "0x8057000d (NS_ERROR_XPC_CANT_CONVERT_WN_TO_FUN)" location: "JS frame :: http://localhost/ :: <TOP_LEVEL> :: line 25" data: no]
It is possible to use a canvas without displaying it on the page. You could do the following:
// Create a canvas element
var canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 400;
// Get the drawing context
var ctx = canvas.getContext('2d');
// Then you can do stuff, e.g.:
ctx.fillStyle = '#f00';
ctx.fillRect(20,10,80,50);
Once you've used the canvas, you can of course add it to the document
var element = document.getElementById('canvas_container');
element.appendChild(canvas);
Or you could make an image from it:
var new_image_url = canvas.toDataURL();
var img = document.createElement('img');
img.src = new_image_url;
Or you could access the canvas data as values with:
var image_data = ctx.getImageData(0, 0, canvas.width, canvas.height);
var rgba_byte_array = image_data.data;
rgba_byte_array[0]; // red value for first pixel (top left) in the canvas
Interestingly enough, if you create a canvas object and store its context in a variable, that variable has its own pointer to the canvas object. Since you can't use getContext("2d") without a canvas, you might as well only have one canvas pointer. If you're like me and hate having a two references to the same object, you could do this:
Original:
var canvas=document.createElement("canvas");
var context=canvas.getContext("2d");
alert(Boolean(context.canvas==canvas));// true.
What I'm talking about:
var context=document.createElement("canvas").getContext("2d");
alert(context.canvas);// The canvas object.
Now you can do all of your important canvas stuff through the context variable. After all, context is accessed more often than the canvas variable. When you do need it just reference it through the context:
context.canvas.width=320;
context.canvas.height=320;
document.body.appendChild(context.canvas);
And if you don't want to bother with the canvas just leave the variable alone, it's not like you wanted to use it anyway.
How about: OffscreenCanvas()?
And the answer is probably no, since this is only supported in Firefox 44.0+ currently.
var offscreen = new OffscreenCanvas(256, 256);
https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
https://html.spec.whatwg.org/multipage/scripting.html#the-offscreencanvas-interface
(Added for reference here, as this may well be new to the spec since this question was asked more than 6 years ago! And hopefully will be more widely adopted)

Apply existing reference to CanvasRendingContext2D to an element

I am trying to store a canvas reference in a global object and then apply that reference to an element instead of regenerating the canvas. here is my existing code. I hope that makes sense. thanks in advance!
waveformCache is assumed to be a global
var cL = document.getElementById('track' + trackId + 'WaveformL');
var cR = document.getElementById('track' + trackId + 'WaveformR');
if (waveformCache.hasOwnProperty(track.path))
{
var waveformCacheItem = waveformCache[track.path];
if (waveformCacheItem.hasOwnProperty('left'))
{
// restore canvas data here to cL element
}
}
else
{
waveformCache[track.path] = {};
var left = track.data.getChannelData(0);
var ctx1 = cL.getContext('2d');
ctx1.save();
ctx1.strokeStyle = 'rgb(49,73,11)';
ctx1.translate(0, 55/2); //centers where the line drawing starts horizontally
for(var i = 0; i < left.length; i += 200) {
var x1 = Math.floor(track.waveformLength * i / left.length); //first parameter affects the length of the drawn waveform #ZOOM
var y1 = left[i] * 55/2;
ctx1.beginPath();
ctx1.moveTo(x1, 0);
ctx1.lineTo(x1 + 1, y1);
ctx1.stroke();
}
ctx1.restore();
waveformCache[track.path].left = ctx1;
}
An outline of how to serialize an html5 canvas CanvasRendingContext2D
The canvas context (CanvasRendingContext2D ) holds the canvas' properties (styling, current transformation, etc).
Important! The context does not hold all the executed drawing commands that created the canvas content.
Context Properties:
Coloring: strokeStyle, fillStyle(1), globalAlpha,
Line styles: lineWidth, lineCap, lineJoin, miterLimit,
Text Styles: font, textAlign, textBaseline,
Compositing: globalCompositeOperation,
Shadowing: shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY
(1) fillStyle is usually a string ('#ff0000'), but it can alternatively hold a reference to a gradient object or pattern object. To save the context's fillStyle, you will have to either ignore gradients / patterns or also serialize the gradient / pattern properties.
Here's how to save context properties into an object
var properties=['strokeStyle','lineWidth','font','globalAlpha',
'globalCompositeOperation','shadowColor','shadowBlur',
'shadowOffsetX','shadowOffsetY','lineCap','lineJoin',
'miterLimit','textAlign','textBaseline'];
var serializedContext={}
for(var i=0;i<properties.length;i++){
var prop=properties[i];
serializedContext[prop]=context[prop];
}
// fillStyle can be a headache
if(typeof context.fillStyle === 'string'){
serializedContext['fillStyle']=context.fillStyle;
}else{
// do lots more work to serialize gradient or pattern :-O
}
Here's how to copy saved context properties into a new context:
var context=myOtherCanvas.getContext('2d');
for(var i=0;i<properties.length;i++){
var prop=properties[i];
context[prop]=serializedContext[prop];
}
// fillStyle can be a headache
if(typeof context.fillStyle === 'string'){
serializedContext['fillStyle']=context.fillStyle;
}else{
// do lots more work to re-establish gradient or pattern :-O
}
Re-executing the drawings
If you want to re-execute all the drawings commands, you must save the commands and their arguments.
From your example code, it looks like your drawings involve line segments(moveTo & lineTo) so you can save each segment as a segment-object in an array of segment-objects.
var segments=[];
segments.push({moveX:10, moveY:20, lineX:100, lineY:35});
... and push all the other line segments
And then you can "replay" the line-segment drawing commands after you've reset all the context properties:
// redraw every line segment
ctx.beginPath()
for(var i=0;i<segments.length;i++){
var s=segments[i];
ctx.moveTo(s.moveX,s.moveY);
ctx.lineTo(s.lineX,s.lineY);
}
ctx.stroke();
You can also serialize and replay all the common drawing commands (arc, beginPath, bezierCurveTo , clearRect, clip, closePath, fill, fillRect, fillText, lineTo, moveTo, quadraticCurveTo, rect, restore, rotate, save, scale, setTransform, stroke, strokeRect, strokeText, transform, translate). Save each command name & associated arguments in an object and save all those command-objects in an array.
These commands return values so you will need to do more work to handle them:
measureText, getImageData (putImageData), toDataURL, isPointInPath, isPointInStroke, createImageData, createLinearGradient, createRadialGradient, createPattern. Luckily, these commands are used are used less often than the more common (simpler) commands.
About portability
If you use this method of saving all properties & drawing commands into object arrays, you can easily serialize them all into JSON strings with JSON.stringify and you can easily deserialize them back into object arrays with JSON.parse.
Having your canvas properties & drawing commands serialized to strings means that you can easily transport them to a server for storage and then fetch them for replaying.
You can use a Path2D object to store your paths commands at. Then store the path in your global object. When you need to re-apply the path, simply stroke or fill using the stored path object.
For example:
var path = new Path2D();
...
path.moveTo(.. , ..);
path.lineTo(.. , ..);
etc.
Later when you need to recall the path:
ctx.stroke(path);
(A bonus is that you can initialize it using SVG paths. This means you can just define your path using SVG commands and store that as a single string. Reapply using the route of Path2D at a slight cost of performance when initializing.)
Path2D can be polyfilled for browsers which do not yet support it (see notes for special cases).

Categories