Dynamically adding new fabric canvases - javascript

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

Related

How to create a texture from multiple graphics

I'm new to PixiJS and I'm trying something simple like a painting application.
I'm having difficulty trying to capture a collection of shapes as a single grouping. I'm not interested in working code for this as I'd like to figure that out on my own; I'm simply interested in knowing whether I'm on the right track or if I need to explore some other PixiJS concepts to get what I need.
I have one canvas in which I can drag shapes such as rectangles, ellipse, and lines. These "strokes" are being stored as individual Graphics objects, for instance:
var shape = new PIXI.Graphics();
shape.position.set(...);
...
shape.lineStyle(...)
.beginFill(...)
.drawRect(...)
.endFill();
...
stage.addChild(shape);
...
renderer.render(stage);
I'm also holding onto these shapes in an array:
shapes.push(shape);
Now that I have these displayed as well as have the order of the strokes available, I'd like to be able to capture them somehow. Imagine maybe taking the drawing and saving it, or perhaps using it as a thumbnail in a gallery, or simply just storing it on the back-end in a database, preferably keeping all the raw strokes so that they can be scaled up or down as desired.
For now, I'm simply trying to take this collection of strokes and display them again by holding them, clearing the graphics from my canvas, and then plopping down what I have held.
Looking at this example, I've been able to get a texture that I can reliably reproduce wherever I click with the mouse:
http://jsfiddle.net/gzh14bcn/
This means I've been able to take the first part that creates the texture object, and I tweaked the second part to create and display the sprites when I click the mouse.
When I try to replace this example code with my own code to create the texture itself, I can't get that part to work.
So this example snippet works fine when I try to create a sprite from it:
var texture = new PIXI.RenderTexture(renderer, 16, 16);
var graphics = new PIXI.Graphics();
graphics.beginFill(0x44FFFF);
graphics.drawCircle(8, 8, 8);
graphics.endFill();
texture.render(graphics);
FYI to create sprites:
var sprite = new PIXI.Sprite(texture);
sprite.position.set(xPos, yPos);
stage.addChild(sprite);
Since I have my shapes in the shapes array or on the stage, what is the preferred way I proceed to capture this as a single grouping from which I can create one or more sprites?
So basicaly you've got how to make some PIXI.Graphics shape
var pixiRect = new PIXI.Graphics();
pixiRect.lineStyle(..);
pixiRect.beginFill(..);
pixiRect.drawRect(..);
pixiRect.endFill(..);
(You can draw as many rects/circles/shapes as you want into one PIXI.Graphics)
But to convert it to texture you must tell renderer to create it
var texture = renderer.generateTexture(pixiRect);
Then you can easily create PIXI.Sprite from this texture
var spr = new PIXI.Sprite(texture);
And the last thing is to add it to your stage or array, but you can also make some empty PIXI.Container and then addChild to that and you've got your array
option - add sprite (created from graphics) to stage
stage.addChild(spr);
option - push it to your array
shapes.push(spr);
option - if you have var shapes = new PIXI.Container(); you can make a container for your sprites
shapes.addChild(spr);
Working example : https://jsfiddle.net/co7Lrbq1/3/
EDIT:
to position your canvas above you have to addChild it later, it means first addChild has zIndex = 0 and every addChild adds a layer on top of last
I figured it out. My stage is a container:
var stage = new PIXI.Container();
var canvas = new PIXI.Graphics();
canvas.lineStyle(4, 0xffffff, 1);
canvas.beginFill(0xffffff);
canvas.drawRect(canvasStartX, canvasStartY, 500, 600);
canvas.endFill();
stage.addChild(canvas);
I changed this to the following:
var canvas = new PIXI.Container();
var canvasRect = new PIXI.Graphics();
canvasRect.lineStyle(4, 0xffffff, 1);
canvasRect.beginFill(0xffffff);
canvasRect.drawRect(canvasStartX, canvasStartY, 500, 600);
canvasRect.endFill();
canvas.addChild(canvasRect);
stage.addChild(canvas);
Then, I replaced stage with canvas where appropriate and canvas with canvasRect where appropriate.
Finally, I got my texture with:
var texture = canvas.generateTexture(renderer);
At the moment, this grabbed the entire width/height of the stage, but I think I just need to tweak a bit on how I create my canvas above and I should be fine.

Scope of document elements created in javascript

I'm new to javascript and web development in general, and I'm trying to write a renderer that can draw each slice in a CT scan as a 2D image.
I have a long thin (512x49664) image made from 512x97 slices, each of which is just a 512x512 image. I've already ascertained that this will upset webgl, so I was planning to grab individual slices from the image by drawing it on a canvas and copying the image data into a texture.
My question is: if I have a function in which I do something like:
// Create a small canvas to contain a single slice.
function getSlice(sliceNumber){
var sliceCanvas = document.createElement("canvas");
sliceCanvas.width = 512;
sliceCanvas.height = 512;
var sliceContext = sliceCanvas.getContext('2d');
sliceContext.drawImage(image, 0, 512*sliceNumber, 512, 512, 0, 0, 512, 512);
}
What happens to the canvas I created when the function exits?
It hasn't been:
Added to the DOM
Stored in a variable or property that is still in scope
Returned anywhere
… there are no references remaining to it, so it will be marked for garbage collection.
When you create a DOM element via javascript DOM API, you should attach this element to the page document.
Otherwise this element will never shown in your page.
So you have to add a line like this in your code:
document.body.appendChild(sliceCanvas);
If you call your function multiple times, you should check the canvas creation:
var sliceCanvas = document.getElementsByTagName('canvas')[0];
or
var sliceCanvas = document.getElementById('myCanvasId');
Then check:
if (!sliceCanvas) {
sliceCanvas = document.createElement('canvas');
sliceCanvas.id = 'myCanvasId'; // optional
document.body.appendChild(sliceCanvas);
}
// here your code...
UPDATE:
Consider to change the document.body with the proper DOM element where you want to place your canvas.

How to address a shape drawn on a HTML5 canvas and change its properties?

I am beginning to explore the HTML5 canvas, and I apologize in advance for the naivety of my question. Using Flash CC, I have generated a canvas with a rectangle on it:
(function (lib, img, cjs, ss) {
var p; // shortcut to reference prototypes
// library properties:
lib.properties = {
width: 550,
height: 400,
fps: 24,
color: "#FFFFFF",
manifest: []
};
// symbols:
// stage content:
(lib.canvas_test = function() {
this.initialize();
// Layer 1
this.shape = new cjs.Shape();
this.shape.graphics.beginFill().beginStroke("#669966")
.setStrokeStyle(1,1,1).moveTo(-94,-62).lineTo(94,-62).lineTo(94,62).lineTo(-94,62).closePath();
this.shape.setTransform(198,136);
this.shape_1 = new cjs.Shape();
this.shape_1.graphics.beginFill("#FF933C")
.beginStroke().moveTo(-94,62).lineTo(-94,-62).lineTo(94,-62).lineTo(94,62).closePath();
this.shape_1.setTransform(198,136);
this.addChild(this.shape_1,this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(378,273,190,126);
})(lib = lib||{}, images = images||{}, createjs = createjs||{}, ss = ss||{});
var lib, images, createjs, ss;
Now I am stuck. How can I retrieve (and change) the color of the rectangle using a Javascript function? I had hoped that the shapes would simply be children of the canvas, but this does not seem to be the case.
The earlier answers are correct about Canvas being basically a bitmap, but EaselJS gives you a retained graphics mode, so you can change properties and update the stage/canvas to reflect them.
You are using Flash export to generate your content, so you should be able to access your elements via the exportRoot, which is created in the HTML. This is essentially the Flash "stage", represented by an EaselJS container that is defined by canvas_test in your exported library.
exportRoot = new lib.canvas_test();
You can see in the canvas_test code, each "child" is defined. Any graphics are wrapped in EaselJS Shape instances. There are also classes for handling groups (Containers), Bitmaps, Text, and animations (MovieClips).
Here is your exported code above put added to the stage:
http://jsfiddle.net/lannymcnie/b5me4xa2/
It is easy to modify shapes once they are created, but you have to define them with that in mind. The Flash export doesn't really provide you this capability, since it just exports everything as a single, chained graphics instructions list. You can however introspect it fairly easily to find the commands you want to modify. Warning: This requires EaselJS 0.7.0+ in order to work. Earlier versions will not work with this approach
The demo you provided has a single Rectangle. Unfortunately there is a bug in the current version of Flash that exports it as 2 shapes, one for the stroke, and another for the fill. This example will modify the stroke.
var shape = exportRoot.shape; // Access the shape instance that has the stroke
var stroke = shape.graphics._stroke;
stroke.style = "#ff0000"; // Set to red.
To do the fill, you can do the same thing on shape_1, but affect the _fill instead. Here is an updated sample
You can also access any of the instructions, and affect their properties. You can see a full command list in the Graphics docs (see the sidebar for the full list). Here is a quick sample modifying the first moveTo command on the stroke:
var shape = exportRoot.shape;
shape.graphics._activeInstructions[0].x = -110;
You can see a sample of that code here: http://jsfiddle.net/lannymcnie/b5me4xa2/2/ -- You will have to modify both fill and stroke to move them both :)
Canvas is basically a bitmap, it has no children. An SVG works more like you're imagining but a canvas just has pixels. If you want to change a canvas you're either going to have to go through it and find the pixels, or create a javascript object representing your drawing object (the rectangle), keep it separate from your canvas background, and redraw the the background and object when there are any changes.
[Added]
I'm not familiar with Flash CC, but as pointed out in the comment, perhaps there is some capability there already to do what the commenter and myself are describing - I'm afraid I don't know.

Get the canvas object while using fabric js

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);

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)

Categories