HTML5 Canvas: Setting context on resize - javascript

I have a project using HTML5 Canvas (createjs) and I've had issues with spikes on text strokes, meaning I have to set the miterlimit of the 2d context. However, the parent (which I have no control over) scales the canvas when the window is resized, which obviously resets the canvas context.
Now, I want to avoid putting an onresize event inside the client - my first thought is just to use the createjs Ticker thus:
createjs.Ticker.addEventListener("tick", handleTick);
function handleTick(event) {
var ctx = canvas.getContext('2d');
ctx.miterLimit = 2;
}
Though this seems a little wasteful, is there a more efficient way of doing this, without using DOM events?

Your approach might work, but its definitely a hack, since you can't expect that context properties will be maintained, or that they won't be applied in the wrong place.
If you do want to patch the display list to update the context, you can use the "drawstart" event, which fires before the display list is drawn:
stage.on("drawstart", function(e) {
var ctx = stage.canvas.getContext("2d");
ctx.miterLimit = 2;
});
However if you want a better approach that is instance-specific, you can extend the Text class to append any context properties you want. Here is a quick example where miterLimit is stored and applied any time the text is drawn. In this example, you can create multiple instances and set different miter limits on them. Note that you might want to also support other properties such as lineJoin.
http://jsfiddle.net/cr4hmgqp/2/
(function() {
"use strict"
function MiterText(text, font, color, miterLimit) {
this.Text_constructor(text,font,color);
this.miterLimit = miterLimit;
};
var p = createjs.extend(MiterText, createjs.Text);
p.draw = function(ctx, ignoreCache) {
ctx.miterLimit = this.miterLimit;
if (this.Text_draw(ctx, ignoreCache)) { return true; }
return true;
};
p.clone = function() {
return this._cloneProps(new MiterText(this.text, this.font, this.color, this.miterLimit));
};
createjs.MiterText = createjs.promote(MiterText, "Text");
}());
Note that this issue should hopefully be fixed in the next version of EaselJS. Here is the tracked issue: https://github.com/CreateJS/EaselJS/issues/781
Cheers.

Related

How do I obtain pixel data from a canvas I don't own?

I am trying to get the pixel RGBA data from a canvas for further processing. I think the canvas is actually a Unity game if that makes a difference.
I am trying to do this with the canvas of the game Shakes and Fidget. I use the readPixels method from the context.
This is what I tried:
var example = document.getElementById('#canvas');
var context = example.getContext('webgl2'); // Also doesn't work with: ', {preserveDrawingBuffer: true}'
var pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4);
context.readPixels(0, 0, context.drawingBufferWidth, context.drawingBufferHeight, context.RGBA, context.UNSIGNED_BYTE, pixels);
But all pixels are black apparently (which is not true obviously).
Edit: Also, I want to read the pixels multiple times.
Thanks everyone for your answers. The answer provided by #Kaiido worked perfectly for me :)
You can require a Canvas context only once. All the following requests will either return null, or the same context that has been created before if you passed the same options to getContext().
Now, the one page you linked to didn't pass the preserveDrawingBuffer option when creating their context, which means that to be able to grab the pixels info from there, you will have to hook up in the same event loop as the one the game loop occur.
Luckily, this exact game does use a simple requestAnimationFrame loop, so to hook up to the same event loop, all we need to do is to also wrap our code in a requestAnimationFrame call.
Since callbacks are stacked, and that they do require the next frame from one such callback to create a loop, we can be sure our call will get stacked after their.
I now realize it might not be obvious, so I'll try to explain further what requestAnimationFrame does, and how we can be sure our callback will get called after Unity's one.
requestAnimationFrame(fn) pushes fn callback into a stack of callbacks that will all get called at the same time in First-In-First-Out order, just before the browser will perform its paint to screen operations. This happens once in a while (generally 60 times per second), at the end of the closest event loop.
It can be understood as a kind of setTimeout(fn , time_remaining_until_next_paint), with the main difference that it is guaranteed that requestAnimationFrame callback executor will get called at the end of the event loop, and thus after other js execution of this event loop.
So if we were to call requestAnimationFrame(fn) in the same event loop that the one where the callbacks will get called, our fake time_remaining_until_next_paint would be 0, and fn will get pushed at the bottom of our stack (last in, last out).
And when calling requestAnimationFrame(fn) from inside the callbacks executor itself, time_remaining_until_next_paint would be something around 16, and fn will get called among the first ones at the next frame.
So any calls to requestAnimationFrame(fn) made from outside of the requestAnimationFrame's callbacks executor is guaranteed to be called in the same event loop than a requestAnimationFrame powered loop, and to be called after.
So all we need to grab these pixels, is to wrap the call to readPixels in a requestAnimationFrame call, and to call it after Unity's loop started.
var example = document.getElementById('#canvas');
var context = example.getContext('webgl2') || example.getContext('webgl');
var pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4);
requestAnimationFrame(() => {
context.readPixels(0, 0, context.drawingBufferWidth, context.drawingBufferHeight, context.RGBA, context.UNSIGNED_BYTE, pixels);
// here `pixels` has the correct data
});
Likely you either need to read the pixels in the same event as they are rendered, or you need to force the canvas to use preserveDrawingBuffer: true so you can read the canvas at any time.
To do the second override getContext
HTMLCanvasElement.prototype.getContext = function(origFn) {
const typesWeCareAbout = {
"webgl": true,
"webgl2": true,
"experimental-webgl": true,
};
return function(type, attributes = {}) {
if (typesWeCareAbout[type]) {
attributes.preserveDrawingBuffer = true;
}
return origFn.call(this, type, attributes);
};
}(HTMLCanvasElement.prototype.getContext);
Put that at the top of the file before the Unity game OR put it in a separate script file and include it before the Unity game.
You should now be able to get a context on whatever canvas Unity made and call gl.readPixels anytime you want.
For the other method, getting pixels in the same event, you would instead wrap requestAnimationFrame so that you can insert your gl.readPixels after Unity's use of requestAnimationFrame
window.requestAnimationFrame = function(origFn) {
return function(callback) {
return origFn(this, function(time) {
callback(time);
gl.readPixels(...);
};
};
}(window.requestAnimationFrame);
Another solution would be to use a virtual webgl context. This library shows an example of implementing a virtual webgl context and shows an example of post processing the unity output
note that at some point Unity will likely switch to using an OffscreenCanvas. At that point it will likely require other solutions than those above.
Alternatively, you can stream the content of the canvas to a video element, draw the video's content to another canvas and read the pixels there.
This should be independent of the frame being painted by a requestAnimationFrame, but is asynchronous.
We need a video, another canvas and a stream:
var example = document.getElementById('#canvas');
var stream=example.captureStream(0);//0 fps
var vid=document.createElement("video");
vid.width=example.width;
vid.height=example.height;
vid.style="display:none;";
document.body.appendChild(vid);
var canvas2=document.createElement("canvas");
canvas2.width=example.width;
canvas2.height=example.height;
canvas2.style="display:none;";
var width=example.width;
var height=example.height;
body.appendChild(canvas2);
var ctx = canvas2.getContext('2d');
Now you can read the game canvas by requesting a frame from the stream, pushing it into the video and painting the video onto our canvas:
stream.requestFrame();
//wait for the game to draw a frame
vid.srcObject=stream;
//wait
ctx.drawImage(vid, 0, 0, width, height, 0, 0, width, height);
var pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4);
ctx.readPixels(0, 0, ctx.drawingBufferWidth, ctx.drawingBufferHeight, ctx.RGBA, ctx.UNSIGNED_BYTE, pixels);

p5 canvas static after being cloned

Following instructions from this question, I have a div which is being cloned that has a p5 canvas inside it. When it is cloned the canvas is not responsive to mouse coordinates.
How do I make the p5 canvas still active after cloning?
$(document).ready(function() {
$(".showcanvas").click(function() {
// Find the original canvas element
var origCanvas = $(".canvas").first().find('canvas')[0];
// Clone the div wrapper
var clonedDiv = $(".canvas").first().clone(true);
// Find the cloned canvas
var clonedCanvas = clonedDiv.find('canvas');
// Update the ID. (IDs should be unique in the document)
clonedCanvas.prop('id', 'defaultCanvas' + $(".canvas").length)
// Draw the original canvas to the cloned canvas
clonedCanvas[0].getContext('2d').drawImage(origCanvas, 0, 0);
// Append the cloned elements
clonedDiv.appendTo("article").fadeIn('1200');
});
});
https://jsfiddle.net/vanderhurk/12fxj48h/28/
I was going to comment on your previous question about this. The approach you're taking of cloning the canvas element and then drawing the old canvas into the new canvas is going to have exactly this problem: the new canvas is not going to change as the old canvas changes. This might work if you just want to take a snapshot (although there are easier ways to do this), but my guess is it's not what you're looking for.
Instead of cloning the element and dealing with JQuery, I suggest you look into using instance mode from the P5.js library. You can read more about instance mode here, but basically it allows you to package your P5.js code in a self-contained object.
Here's a simple example:
var s = function( sketch ) {
var x = 100;
var y = 100;
sketch.setup = function() {
sketch.createCanvas(200, 200);
};
sketch.draw = function() {
sketch.background(0);
sketch.fill(255);
sketch.rect(x,y,50,50);
};
};
var myp5 = new p5(s);
If I were you, I would encapsulate the creation of an instance-mode P5.js sketch into a function or class. Then whenever I want to create a new copy of the sketch, I'd call that function or class. You could also store any shared state in global variables that both sketches access.

How to ensure correct execution order of complex canvas manipulation

I have some code that renders specific DOM elements to canvas, sort of like taking a screenshot. (It's custom code built for a very particular DOM structure as part of a graphics editing game, not a general library like rasterHTML.js)
The code flow is pretty procedural:
get some DOM elements of class A and draw them to canvas
get some DOM elements of class B and draw them to canvas
The trouble is that step 1 is very intensive compared to step 2, and doesn't finish drawing before step 2, screwing up the layers (in reality I have several canvases doing several things at once, and a canvas is unfortunately resized before all the drawing is completed). I've tried to replicate this in this fiddle: https://jsfiddle.net/1hucuLg9/
In pseudocode:
context.drawComplexSVG(); // slow
context.drawSimpleImage(); //fast
//canvas now has an SVG drawn on top of an image, not underneath.
I've seen a lot of setTimeout examples in an attempt to get one function to execute after another, but to me this seems to be a bit of a hack ... ideally I don't want to delay execution, just execute everything in strict order. I've also seen the idea of postMessage floated to achieve this but I've no idea how you pass messages to yourself. What's the correct way to ensure a function/line is fully executed (or in my case, the canvas is fully updated - is it the same thing?) before proceeding?
"getting some DOM elements" should be synchronous and do not require any complex code to handle sequencing draw operations.
The problem you are facing in your fiddle is that you are dynamically loading some images to draw - and for those, you need to wait, which makes the operation asynchronous.
Promises are here for your rescue, but you'll have to use them correctly. Just calling resolve right away like you did in your own answer will ensure some asynchrony, but that's not less fragile than a setTimeout approach. Instead, you should always create the promise at the heart of the asynchrony, in your case the image loading:
function loadImage(src) {
return new Promise(resolve, reject) {
var img = new Image();
img.onload = function(){ resolve(img); };
img.onerror = reject;
img.src = src;
});
}
so that you can use it in your canvas drawing code:
function drawSwatches(currentSwatch) {
var data = …;
var url = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(data);
return loadImage(url).then(function(img) {
ctx.drawImage(img, 0, 0, vw, vh);
});
}
and then chain these properly:
var swatches = Array.from(document.getElementsByClassName("swatch"));
swatches.reduce(function(promise, swatch) {
return promise.then(function() {
return drawSwatches(swatch);
});
}, Promise.resolve()).then(function() {
var otherObjects = document.getElementsByClassName("otherObjects");
for (var i=0; i<otherObjects.length; i++) {
drawOtherObjects(otherObjects[i], 0, 0, 100, 100);
}
});
Well, it seems that this will get the job done:
var promise = new Promise(function(resolve){
context.drawComplexSVG();
resolve();
}
promise.then(function(){
context.drawSimpleImage();
}

Odd bug with drawImage on HTML5 canvas

JsFiddle (note: It doesn't show anything, it's merely a way for me to show my code in a neater format) http://jsfiddle.net/h6tVR/
I am new to HTML5 canvas and have decided to play about and see what I can do with it. So far I've been able to draw a locally hosted image onto the canvas and even do a bit of basic tiling:
window.onload = function(){
var GameClosure = function() {
var canv = document.getElementById("canv");
var canvContext = canv.getContext("2d");
var sprite = new Image();
sprite.src = "sprite.png"
var tile = new Image();
tile.src = "tile.png"
function loadSprite(){
sprite.onload = function(){
canvContext.drawImage(sprite, 50, 50);
};
}
function loadTiles(){
tile.onload = function(){
for(var i = 0; i < 800; i += 16){
for(var r = 0; r < 608; r += 16){
canvContext.drawImage(tile, i, r);
}
}
};
}
return{
loadTiles: loadTiles,
loadSprite: loadSprite
};
}();
GameClosure.loadTiles();
GameClosure.loadSprite();
}
I am getting an odd problem with this. When I load it up, the majority of the time, only the tiles will load up. I've tried a couple of things so far, I've switched the GameClosure.loadTiles() and GameClosure.loadSprite(); calls to see if the load order made any difference. It doesn't. I even tried creating a second context and assigning the tiles to one and the sprite to another, but this made no difference. Commenting out the tile call produces the sprite correctly.
It gets even odder. I was refreshing the page rapidly and I noticed that occasionally (with no pattern to it, sometime it could happen 3 times in a row, other time once in 20) the tiles would load AND the sprite would load on top as I would expect it to.
Can this be fixed? My only guess is that my code is running somewhat asyncronously and the for loops creating the tiles are completing after the sprite has been loaded, but looking at my code I don't see where this could be happening.
Separate the concerns. Wait for all resources to be loaded (and the document), then launch your game. Always be sure to hook event handler before assigning src to avoid 'random' (cache-related, in fact) behaviors.
When you set an onload handler, your javascript will continue while the resource loads in the background. The handler will be executed when the resource has loaded. You have no way to tell when that will happen and in what order.
When you have multiple resources and want to call your draw-function the moment the last one has loaded, you could have a global preloader-object. Each onload-handler should call a function on the preloader to inform it that the resource has loaded. That function should check if all resources have reported in, and when that's the case execute the draw-function.
Also, when you set an onload-handler and the resource is already loaded. When you set .src and the resource is in the browsers cache, it will get loaded instantly. So you always need to first set .onload and then set .src.

Lock/unlock HTML5 canvas

Is there any possibility of locking canvas element? In my app I draw complex images and sometimes I use bitmaps. Using bitmaps with canvas isn't quite comfortable - all the drawing that should be placed on canvas after bitmap is placed in bitmaps .onload.
It would be a lot easier if I could lock and unlock canvas so it couldn't be updated for some time.
AFAIK there is no built-in function for lock/unlock. Do you know any simple way of implementing it?
I don't know whether this is bulletproof or not, but you could temporarily make .stroke etc. noops: http://jsfiddle.net/eGjak/247/.
var toLock = "stroke fill".split(" ");
function lock() {
$.each(toLock, function(i, name) { // or another way to loop the array
ctx[name] = function() {};
});
}
function unlock() {
$.each(toLock, function(i, name) {
ctx[name] = ctx.constructor.prototype[name];
});
}
$("canvas").attr("disable","False");
// bitmap .onload...
$("canvas").attr("disable","True");
Or
bitmap.onload = function(){
var canvas = window.getCanvas();
});

Categories

Resources