Is it possible to create an HTML canvas without a DOM element? - javascript

I'd like to have an HTML canvas context that I can paint to and read off-screen (in this example, writing text and reading the shape that is created, but it's a general question). I may also want to use a canvas as an off-screen frame-buffer.
I suppose I could create a hidden DOM element but I'd rather create it from JavaScript (I may want to create and destroy a number of canvas at runtime).
Possible?

You can create a new canvas element with document.createElement:
var canvas = document.createElement('canvas');
and then get the context from it. Just make sure you set the width and height. You don't have to add the canvas to the tree in order to make it work:
DEMO
But you definitely have to create that node. You could create a function for that though:
function createContext(width, height) {
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas.getContext("2d");
}
But that is where my competency ends... whether you can somehow transfer a context to another context or canvas, I don't know...

Its old but what about saving one canvas with toDataURL and copying to the other with drawImage. you could also use save and restore to make a frame buffer
function createCanvas(width, height) {
var c = document.createElement('canvas');
c.setAttribute('width', width);
c.setAttribute('height', height);
return c;
}
function canvasImg(canvas) {
var ctx = canvas.getContext('2d');
ctx.fillRect(0,0,canvas.width, canvas.height);
var img = canvas.toDataURL('image/png');
return img;
}
function placeImage(canvas, img) {
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0,0);
}
window.onload = function(){
var canvas = createCanvas(400, 400);
var hiddenCanvas = createCanvas(400,400);
var i = canvasImg(hiddenCanvas);
var img = new Image();
img.src = i;
placeImage(canvas, img);
document.body.appendChild(canvas);
}

There is apparently a new thing called OffscreenCanvas that was deliberately designed for this use case. An additional bonus is that it also works in Web Workers.
You can read the specifications here: https://html.spec.whatwg.org/multipage/canvas.html#the-offscreencanvas-interface
And see examples here: https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
Currently it is only fully supported by Chrome and is available behind flags in Firefox and Opera, but you can always check for the latest information on supported browsers here: https://caniuse.com/#feat=offscreencanvas
ps.: Google also has a dedicated guide explaining it's use in Web Workers: https://developers.google.com/web/updates/2018/08/offscreen-canvas

Both the CanvasRenderingContext2D and WebGLRenderingContext classes have the canvas element associated with them as the property canvas; and, like normal, both context instances and their canvases will be garbage collected when your code no longer makes references to them at run time.
You can use this function to create a new context
function newContext({width, height}, contextType = '2d') {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas.getContext(contextType);
}
const ctx = newContext({width: 100, height: 100});
console.log(ctx.canvas.width == 100) // true
And by making use of dereferencing you can easily create a clone of a DOM canvas for frame buffering like this:
const domCanvas = document.getElementById('myCanvas');
const frameBuffer = newContext(domCanvas);
frameBuffer.drawImage(domCanvas, 0, 0);
Which will create a context with the same width and height as the canvas element passed in. You can extend the function as needed.

Related

TypeScript class .drawImage

I am trying to draw a kitten in an HTML5 canvas through a class constructor using TypeScript but I am confused on how to achieve the task. I have commented the code to show what I have attempted to do based on the behavior that I expected vs what actually works. Thank you very much for your timer and advice.
module Game {
export class Test {
width: number;
height: number;
cellWidth: number;
cellHeight: number;
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
constructor() {
this.width = 28;
this.height = 31;
this.cellWidth = 20;
this.cellHeight = 20;
this.canvas = <HTMLCanvasElement> document.getElementById("game_canvas");
this.context = this.canvas.getContext("2d");
this.canvas.width = this.width * this.cellWidth;
this.canvas.height = this.height * this.cellHeight;
this.context.fillStyle = "blue";
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
let kitten = new Image();
kitten.src = 'img/kitten.png';
// When trying to draw a kitten in the canvas,
// this will work:
kitten.onload = () => {
this.context.drawImage(kitten, 0, 0);
};
// but this work won't:
//this.context.drawImage(kitten, 0, 0);
/*
I was assuming that by accessing the this.context property
I would have direct access to the canvas and I will be able to use
drawImage to draw the kitten on it; however, that approach
produces no kitten in the canvas.
Only by using the .onload method it works.
I am using the () => notation so that the this inside the block
is referring to the class.
I have seen many JavasScript files in which images are simple drawn
through:
context.drawImage(image, 0, 0);
They are not embedded in .onload
I have tried to Google information but I cannot pinpoint what is
happening.
*/
}
}
}
As per my comments here is my answer: quite simply because you are declaring a new Image() and setting the src, your drawImage call will no doubt be in advance of the src being loaded... if you were to use a previously loaded image (e.g. from the DOM) then the creation of a new image and load would not be required
setting the src triggers the load - doing it in another class still subjects you to the wait time for load and you cannot be sure - I would say using onload is bulletproof and essential if you are the loader of the images you are using - the only alternative methods are when the images are already loaded into the DOM (or preloaded elsewhere), you may find the canvas examples you have seen are designed to be initiated onload of the images concerned
From your code:
let kitten = new Image();
kitten.src = 'img/kitten.png';
// When trying to draw a kitten in the canvas,
// this will work:
kitten.onload = () => {
this.context.drawImage(kitten, 0, 0);
};
// but this work won't:
//this.context.drawImage(kitten, 0, 0);
Using onload is the defacto way you get an image that you can draw on a canvas. A microoptimization would be to keep a dictionary of loaded images so you get to reuse them if drawing multiple kittens.

Bitmap image not showing up in canvas

I need some direction here as I'm not clear on what I'm doing wrong. All I'm trying to do is a load a bitmap in the center of the canvas but it's not showing up. My file path is correct and I don't see what I might have coded incorrectly, where am I going wrong?
var canvas, stage, centerX, centerY;
function init() {
'use strict';
canvas = document.getElementById("easel");
stage = new createjs.Stage(canvas);
centerX = canvas.width / 2;
centerY = canvas.height / 2;
var ship = new createjs.Bitmap("../images/millenium.png"),
shipCenterX = ship.width / 2,
shipCenterY = ship.height / 2;
ship.x = centerX;
ship.y = centerY;
ship.regX = shipCenterX;
ship.regY = shipCenterY;
stage.addChild(ship);
stage.update();
}
The way this library appears to handle drawing to the canvas is by calling stage.update() which they recommend attaching to their "tick" event (e.g. http://www.createjs.com/docs/easeljs/classes/Stage.html)
Basically, we need to keep continually redrawing the canvas, and createjs gives us a method to do that, like so:
createjs.Ticker.addEventListener("tick", handleTick);
function handleTick(event) {
stage.update();
}
However, since you haven't made your stage globally accessible, I tweaked your init function slightly so that we can access stage by returning it at the end of the function. Thus you can set stage in the global scope to the result of the function:
var stage = init();
And handleTick will use that stage by default. However if you're thinking of reusing your objects outside of your init() function, you may want to consider passing them to the init function or keeping their initial data structure outside of the init function to make them easier to access.
https://jsfiddle.net/jpgw1oka/
And make sure you are loading the CreateJS library: https://code.createjs.com/createjs-2015.11.26.min.js.
Remove the 'use strict'.
And make sure you are declaring your variables and function calls inside a window.onload event listener.
E.g.
var canvas, stage;
function init() {
canvas = document.getElementById("easel");
stage = new createjs.Stage(canvas);
var ship = new createjs.Bitmap("../images/millenium.png");
ship.x = Math.floor(stage.canvas.width * 0.5);
ship.y = Math.florr(stage.canvas.height * 0.5);
ship.regX = Math.floor(ship.image.width * 0.5);
ship.regY = Math.floor(ship.image.height * 0.5);
stage.addChild(ship);
stage.update();
}

Html5 canvas not drawing

I have a problem with an object not drawing it's image. I've set the onload property of the image to the draw function..
//ctor
function Sprite(someargs,pContext)
this.setContext(pContext); //This could be the problem?
this.setX(px);
this.setY(py);
this.setTexture(pImagePath);
//I run this in the constructor
Sprite.prototype.setTexture = function(pImagePath){
this.texture = new Image();
this.texture.onload = this.draw();
this.texture.src = pImagePath;
};
Sprite.prototype.draw = function(){
this.getContext().drawImage(this.texture,this.getX(),this.getY(),100,100);
};
Sprite.prototype.setContext = function(pContext){
this.mContext = pContext;
};
No errors at runtime, image not drawn onto canvas though.
I'v put alerts in all the above methods, all of which are being executed.
Anyone got any ideas as to why it's not drawing?
Cheers
this.texture.onload = this.draw();
you are not setting onload to the draw function but to the result of draw function
this.texture.onload = this.draw;
also wouldn't be good because you will lose your context of this in here.
this inside of draw function will point to texture instead of Sprite
you need to bind the function draw to this (which is the Sprite at the moment) and pass it to onload
this.texture.onload = this.draw.bind(this);
or:
var that = this;
this.texture.onload = function() { that.draw(); }

How to hide and restore custom nodes in InfoVis/JIT force directed graph?

I am trying to use InfoVis / JIT to render a force directed graph visualizing a network.
I am a newbie to both java script and JIT.
I have created my own custom node types using following code in my js file, which lets me display my image on the node.
$jit.ForceDirected.Plot.NodeTypes.implement({
'icon1': {
'render': function(node, canvas){
var ctx = canvas.getCtx();
var img = new Image();
img.src='magnify.png';
var pos = node.pos.getc(true);
img.onload = function() {
ctx.drawImage(img, pos.x, pos.y);
};
},
'contains': function(node,pos){
var npos = node.pos.getc(true);
dim = node.getData('dim');
return this.nodeHelper.circle.contains(npos, pos, dim);
//return this.nodeHelper.square.contains(npos, pos, dim);
}
}
I am assigning this custom node type to the node using "$type": "icon1" in the json data object. I do get image on the node, but the problem is that I am not able to hide it when required. I am able to hide the in-built node types like circle,square etc. using following code.
node.setData('alpha', 0);
node.eachAdjacency(function(adj) {
adj.setData('alpha', 0);
});
fd.fx.animate({
modes: ['node-property:alpha',
'edge-property:alpha'],
duration: 2000
});
But the same code does not work for custom nodes.
Hence I tried to temporarily change the type of node to the built-in "circle" type, hid it and then re-setted the type of node to its original i.e. my custom node, icon1.
function hideNode( ){
var typeOfNode = node.getData('type');
node.setData( 'type','circle');
node.setData('alpha', 0);
node.eachAdjacency(function(adj) {
adj.setData('alpha', 0);
});
fd.fx.animate({
modes: ['node-property:alpha',
'edge-property:alpha'],
duration: 2000
});
node.setData('type',typeOfNode );
}
I think this should work but the custom image comes back in a while on the canvas.
If I don't reset the type of node to its original i.e. in the above code and comment out the following statement and call hide function, then the node gets hidden.
node.setData('type',typeOfNode );
I am not able to figure out how by only setting a node's type to some custom type, the node is being rendered. Any help with this question will be appreciated.
I need to re-set the node's type to its original because I want the node to be restored when required by calling unhide function. If I don't reset node's type to the original then it would be rendered as a circle when restored.
I have gone through the API and the google group for JIT but couldn't find an answer.
Can anyone help?
Here's a look at a snippet from the Plot's plotNode function:
var alpha = node.getData('alpha'),
ctx = canvas.getCtx();
ctx.save();
ctx.globalAlpha = alpha;
// snip
this.nodeTypes[f].render.call(this, node, canvas, animating);
ctx.restore();
As you can see, the node's alpha value is applied to the canvas immediately before the node's render function is called. After rendering the node, the canvas is restored to the previous state.
The issue here is that your custom node's render function does not render the node synchronously, and the canvas state is getting restored prior to the call to drawImage. So, you can do one of two things:
1) Preload and cache your image (preferred approach, as this will also prevent image flickering and help with performance):
// preload image
var magnifyImg = new Image();
magnifyImg.src = 'magnify.png';
// 'icon1' node render function:
'render': function(node, canvas){
var ctx = canvas.getCtx();
var pos = node.pos.getc(true);
ctx.drawImage(magnifyImg, pos.x, pos.y);
}
or 2) save the canvas state, reapply the alpha, and then restore the canvas state after drawing the image in your onload handler:
// 'icon1' node render function:
'render': function(node, canvas){
var ctx = canvas.getCtx();
var img = new Image();
img.src='magnify.png';
var pos = node.pos.getc(true);
img.onload = function() {
ctx.save(); // save current canvas state
ctx.globalAlpha = node.getData('alpha'); // apply node alpha
ctx.drawImage(img, pos.x, pos.y); // draw image
ctx.restore(); // revert to previous canvas state
};
}

Get canvas from context

Is there any way to obtain the canvas that a context is used for?
Why I'm asking is because I'm creating a prototype function for CanvasRenderingContext2D in which I need the width/height of the canvas element.
E.g.:
var cv = document.getElementById('canvas');
var ctx = cv.getContext('2d');
// Using only 'ctx', how to get 'cv'?
ctx.canvas should return the canvas DOM node, from which you can get height and width.
I tried it with https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_usage
Firefox was able to return ctx.canvas, as well as ctx.canvas.width and ctx.canvas.height. Also confirmed in Chrome.
Try this to check for yourself:
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var isSameObject = canvas === context.canvas;
alert(isSameObject
? 'context.canvas gives expected result'
: 'unexpected result');
Here's the above in a jsFiddle.

Categories