Since downloading the current version of chrome (Version 31.0.1650.57) I've been completely unable to draw images in a HTML5 Canvas with my code; there are no errors and it's finding the resources, they just aren't drawing. I'd really appreciate some help on this!
var grass_img = new Image();
grass_img.src = 'grass.gif';
grass_img.onload = draw_here(grass_img, (center_x + base_x + xpos), (center_y + base_y + ypos),1);
Which appears here and there on several different images and calls:
function draw_here(image, x, y, scale){
draw_canv.drawImage(image, x, y, image.width * scale, image.height * scale);
}
X and Y are correct, as is the scale; there are no coding errors picked up by the debugger and the program worked perfectly until the latest version of chrome came out. Downgrading chrome is also not an option.
The problem is in this line:
grass_img.onload = draw_here( /* ... */ );
You seem to think that you assign the function draw_here as an onload-handler. But that's not what really happens in this line. What you really do is execute draw_here immediately and assign the return-value of that function (which is undefined) to grass_img.onload.
Try this instead:
grass_img.onload = function() {
draw_here(grass_img, (center_x + base_x + xpos), (center_y + base_y + ypos),1);
}
This creates an anonymous function which is assigned to the onload-handler. When that anonymous function is called (which will happen when the load-event is triggered) it calls your draw-handler with your arguments.
I solved the problem. It seems that the latest version of Chrome does not like it if you declare your image in every frame iteration. (Something it seems to have allowed previously, but I really shouldn't have been doing.) If you declare your images and their sources at the top of your js, outside of any functions, and only draw them in functions then the problem is solved, eg:
var image = new Image();
image.src='image.jpg';
someFunction() {
canvas.drawImage(image,0,0);
}
Note: .src should always be called AFTER .onload. Otherwise lots of hard-to-track-down buggies (like this one) appear. Especially if your image loads before onload can be parsed. Your currently accepted solution is possibly functional due to a race condition with function overhead timing.
Related
I am regularly updating several Dygraphs graphs. After some period of time, normally a few minutes, some or all of them get corrupted as shown in the figure below. I haven't been able to tie this a particular event or browser. This happens even with a simple graph where I am just reloading the data stored in a CSV file. I call updateOptions({ file: URL }) on the graph object, where URL points to the CSV file, followed by calling resetZoom() on the graph object to update the axes. Googling hasn't revealed anyone suffering similar behaviour, so I'm lost as to what is causing this.
Update 1: It is linked to minimizing and maximizing the browser.
Update 2: The problem doesn't occur in Firefox. It does happen in Google Chrome and Internet Explorer, although IE has the additional problem of freezing after a while (a problem for another day).
Update 3: Minimum working examples added at http://jsfiddle.net/williamshipman/tvxekq56/ and http://jsfiddle.net/williamshipman/af66qstt/. Repeatedly minimize and maximize the browser window, after a while the distortion occurs. The first example uses AngularJS (like my own work), while the second demonstrates the same bug in pure JavaScript. You may have to minimize and maximize more than a dozen times to see the bug, it seems pretty random.
For me similar problem appears when I show and hide Y2 axis.
This one line helped me: ctx.clearRect(0, 0, this.width, this.height);
File: dygraph-canvas.js
var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
...
ctx = this.dygraph_.hidden_ctx_;
ctx.clearRect(0, 0, this.width, this.height); // <== clear whole canvas before cliping
ctx.beginPath();
ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);
ctx.clip();
};
The root of the problem
Canvas context is not fully restored after all draw is done.
Solution 1. (workaround)
Injecting canvas_ctx_.restore() after draw is done and context.save() before. save() is needed because library is restoring the context before every draw(except the initial one).
let g = new Dygraph('graph', {
underlayCallback: (context) => {
context.save();
},
drawCallback: (dygraph) => {
dygraph.canvas_ctx_.restore();
},
});
Solution 2. (library fix)
Here is my commit you can apply to the lib's src/dygraph.js
https://github.com/pawelzwronek/dygraphs/commit/c66ca37b82f14e096652a338cae8abf568b9c764
I have a javascript timer.
It refreshes the img src on a 200ms interval.
I have taken a look at the canvas object. I am unsure whether it is recommended to use the canvas instead of the img element?
I am running tests on both and cannot see any differences in performance.
This is my code for using the timer/img:
This is my code:
var timer4x4
var cache4x4 = new Image();
var alias = 'test';
var lastUpdate = 0;
function setImageSrc4x4(src) {
live4x4.src = src;
timer4x4 = window.setTimeout(swapImages4x4, 200);
}
function swapImages4x4() {
cache4x4.onload = function () {
setImageSrc4x4(cache4x4.src);
};
cache4x4.onerror = function () {
setImageSrc4x4("http://127.0.0.1/images/ERROR.jpg");
};
cache4x4.src = null;
cache4x4.src = 'http://127.0.0.1/Cloud/LiveXP.ashx?id=' + createGuid() + '&Alias=' + alias + '&ReSync=' + reSync;
reSync = 0;
}
*nb will add canvas code in a bit
I am streaming images from my client desktop PC to my web server. I am trying to display as many images (FPS) as possible. The image is a container for 4 smaller images. Stitched up on the client and sent to the server.
I have Googled and it says if doing pixel manipulation and aniumation use canvas.
But, I am just doing animation.
Thanks
The canvas element was designed to draw / edit / interact with images in it. If all you do is display the image, then you don't need that and a simple img is the semantically correct choice (with the added bonus of being compatible on more devices).
In both cases, the performance will be similar (if not the same) because the only thing to happen is that the image is downloaded.
While performance-wise you won't notice much of a difference, since you still cannot fully rely on HTML5 support yet, it is probably best to go with the img-solution for now.
I'm using some javascript to allow users to dynamically load a sketch on click to a canvas element using:
Processing.loadSketchFromSources('canvas_id', ['sketch.pde']);
If I call Processing.loadSketchFromSources(...) a second (or third...) time, it loads a second (or third...) .pde file onto the canvas, which is what I would expect.
I'd like for the user to be able to click another link to load a different sketch, effectively unloading the previous one. Is there a method I can call (or a technique I can use) to check if Processing has another sketch running, and if so, tell it to unload it first?
Is there some sort of Processing.unloadSketch() method I'm overlooking? I could simply drop the canvas DOM object and recreate it, but that (1) seems like using a hammer when I need a needle, and (2) it results in a screen-flicker that I'd like to avoid.
I'm no JS expert, but I've done my best to look through the processing.js source to see what other functions may exist, but I'm hitting a wall. I thought perhaps I could look at Processing.Sketches.length to see if something is loaded already, but simply pop'ing it off the array doesn't seem to work (didn't think it would).
I'm using ProcessingJS 1.3.6.
In case someone else comes looking for the solution, here's what I did that worked. Note that this was placed inside a closure (not included here for brevity) -- hence the this.launch = function(), blah blah blah... YMMV.
/**
* Launches a specific sketch. Assumes files are stored in
* the ./sketches subdirectory, and your canvas is named g_sketch_canvas
* #param {String} item The name of the file (no extension)
* #param {Array} sketchlist Array of sketches to choose from
* #returns true
* #type Boolean
*/
this.launch = function (item, sketchlist) {
var cvs = document.getElementById('g_sketch_canvas'),
ctx = cvs.getContext('2d');
if ($.inArray(item, sketchlist) !== -1) {
// Unload the Processing script
if (Processing.instances.length > 0) {
// There should only be one, so no need to loop
Processing.instances[0].exit();
// If you may have more than one, then use this loop:
for (i=0; i < Processing.instances.length; (i++)) {
// Processing.instances[i].exit();
//}
}
// Clear the context
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, cvs.width, cvs.height);
// Now, load the new Processing script
Processing.loadSketchFromSources(cvs, ['sketches/' + item + '.pde']);
}
return true;
};
I'm not familiar with Processing.js, but the example code from the site has this:
var canvas = document.getElementById("canvas1");
// attaching the sketchProc function to the canvas
var p = new Processing(canvas, sketchProc);
// p.exit(); to detach it
So in your case, you'll want to keep a handle to the first instance when you create it:
var p1 = Processing.loadSketchFromSources('canvas_id', ['sketch.pde']);
When you're ready to "unload" and load a new sketch, I'm guessing (but don't know) that you'll need to clear the canvas yourself:
p1.exit();
var canvas = document.getElementById('canvas_id');
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
// Or context.fillRect(...) with white, or whatever clearing it means to you
Then, from the sound of things, you're free to attach another sketch:
var p2 = Processing.loadSketchFromSources('canvas_id', ['sketch2.pde']);
Again, I'm not actually familiar with that library, but this appears straightforward from the documentation.
As of processing.js 1.4.8, Andrew's accepted answer (and the other answers I've found in here) do not seem to work anymore.
This is what worked for me:
var pjs = Processing.getInstanceById('pjs');
if (typeof pjs !== "undefined") {
pjs.exit();
}
var canvas = document.getElementById('pjs')
new Processing(canvas, scriptText);
where pjs is the id of the canvas element where the scrips is being run.
I am simply loading a ton of images (about 5000) into "new Image()" objects and draw them each in a canvas by calling canvas.drawImage(image, 0, 0);
This works totally fine with IE10, but as soon as I am using Firefox I will get an stack overflow error, because somehow the memory usage of the Firefox rises and rises till it overflows. Does anyone have an idea why? I think the GC dont really collect my images after drawing them into the canvas. Even when I am using 100 Image objects and just cycle the src of the Image objects before drawing them, the memory usage rises and rises. I will test Chrome and Safari soon, but still need a solution for that, cause everyone is using "the best browser" Firefox.
EDIT:
function play() {
//calculated iLag here
//calculated wondow.FrameCtr here
var iFrameRate = Math.round(1000 / 25);
var oImage = new Image();
oImage.onload = function () {
renderImage(this);
}
//window.Video is an array of window.URL.createObjectURL(data) (about 500 items)
oImage.src = window.Video[window.FrameCtr];
oImage = null;
setTimeout(
function () {
play()
}, iFrameRate - iLag
);
function renderImage(oImage) {
$("#video")[0].getContext("2d").drawImage(oImage, 0, 0);
}
I do loop this video (500 items, 25fps) 10 times, and ff isn't even able to play it once, cause of stack overflow.
As I mentioned before it is working fine with IE10 and works even better with Chrome, so I don't think the problem here is the recursion. Is there any other way to get binary data into in canvas, than using an Image object and setting the src?
It is already noted as a bug in Firefox. You can see the bug report here. It is showing a last modified date of 2010-09-17 but I am not sure has it been resolved in newer version or what.
But I guess newer version of Firefox should not have that problem.
I have a page which has several <canvas> elements.
I am passing the canvas ID and an array of data to a function which then grabs the canvas info and passes the data onto a draw() function which in turn processes the given data and draws the results onto the canvas. So far, so good.
Example data arrays;
$(function() {
setup($("#canvas-1"), [[110,110,100],
[180,180,50],
[220,280,80]]);
setup($("#canvas-2"), [[110,110,100],
[180,180,50],
[220,280,80]]);
});
setup function;
function setup(canvas, data) {
ctx = canvas[0].getContext('2d');
var i = data.length;
var dimensions = {
w : canvas.innerWidth(),
h : canvas.innerHeight()
};
draw(dimensions, data, i);
}
This works perfectly. draw() runs and each canvas is populated.
However - I need to animate the canvas. As soon as I replace line 8 of the above example;
draw(dimensions, data, i);
with
setInterval( function() { draw(dimensions, data, i); }, 33 );
It stops working and only draws the last canvas (with the others remaining blank).
I'm new to both javascript and canvas so sorry if this is an obvious one, still feeling my way around. Guidance in the right direction much appreciated! Thanks.
The problem has to do with how closures work. Closures don't receive a copy of the data, they receive an active reference to it. So when the function is run later, it references a live copy of i and such -- which have moved on since you set up the call.
You can fix it like this:
drawLater(dimensions, data, i);
...with this defined elsewhere:
function drawLater(dimensions, data, i) {
setInterval(function() { draw(dimensions, data, i); }, 33 );
}
That works because the closure holds a reference to the arguments to drawLater, rather than to the variables in your loop.
Separately: Shouldn't you be passing the canvas or its ID into that somewhere?
How does draw() know which canvas to use? My guess from your code is that you're using the global variable ctx which will get overwritten with every call to setup(). So you need to change draw() and add the canvas to use as the first parameter.
JavaScript variables are function scoped, not block scoped. Also you need to declare ctx with var to make it local and then pass it to the draw function.
I think you should be able to make it work if you try the following :
setInterval( 'draw(dimensions, data, i);', 33 );
Hope this helps :)