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.
Related
I need to access and display a commercial webpage which changes depending on the parameters passed. I've simplified it here as a call to: www.mywebpage.com?index=42.
With certain parameters the webpage returns an error page. I want to trap this and do something else.
The code below works. I set up an invisible buffer image and load the webpage to it. Then in the onload() function I draw it onto a canvas element and check some pixel colours (here simplified to checking one byte of one pixel)
If everything is OK. I load the main image.
var imgMain = document.getElementById("imgDisplay");
var imgCopy = new Image();
callURL = "www.mywebpage.com?index=42";
imgCopy.src = callURL;
imgCopy.onload = function() {
var context = document.createElement('CANVAS').getContext('2d');
context.drawImage(imgCopy, 0, 0);.
var pixel1_1 = context.getImageData(0, 0, 1, 1).data;
if(pixel1_1[0] == 228) imgMain.src = callURL;
};
This code only works within the onload() function. Despite the fact that everybody tells me that JavaScript is a synchronous language it's not possible to set imgCopy and check it before going on.
Mainly for readability purposes I would like to be able to delay execution until imgCopy is fully loaded, then call a Boolean function to perform the tests on the image.
Does anybody have any ideas?
Many thanks
Tony Reynolds (UK)
As I didn't get a response I decided not to be so lazy and did some work with Promises. Eventually I came up with:
const promise = new Promise(function(resolve, reject) {
img.src = callURL;
resolve('Success!');
});
promise.then(function(value) {
var context;
context = document.createElement('CANVAS').getContext('2d');
context.drawImage(img, 0, 0);
var pixel1_1 = context.getImageData(0, 0, 1, 1).data;
};
So loading the image is embedded within a Promise, and the .then function I hoped would only execute when the Promise was resolved. It didn't work....
Does anybody know why not?
Thanks
Tony Reynolds
Is your code actually working or did you mis-copy, because the line imgCopy = callURL; makes no sense. It should be imgCopy.src = callURL;. And as Griffin says, set onload before setting src.
Requirement:
Now: Draw on a Canvas, and hit Save (store Canvas state/drawing offline - but NOT as image).
Later: Open up the Canvas with previously saved drawing showing, and continue to draw again.
For drawing we normally use code as follows:
canvas = document.getElementById('can');
ctx = canvas.getContext("2d");
...
ctx.beginPath();
ctx.moveTo(prevX, prevY);
ctx.lineTo(currX, currY);
....
In order to restore Canvas state later - exporting to Image does not help.
I want to restore the Canvas to it's original state to continue editing the drawing at a later date.
I guess, the Canvas context has to be exported and stored offline - how?
Your best shot here is to use a Proxy that will both store the draw commands and perform the drawings.
Since the browser support for Proxy is very bad (only FF as of today), you'll have to build the Proxy yourself, either by using nosuchmethod, or by building a new brand new WatchedContext Class out of the Context2D.
I took the last solution (WatchedContext Class) for this short demo :
function WatchedContext(hostedCtx) {
this.commands= [];
Context2dPrototype = CanvasRenderingContext2D.prototype;
for (var p in Context2dPrototype ) {
this[p] = function(methodName) {
return function() {
this.commands.push(methodName, arguments);
return Context2dPrototype[methodName].apply(hostedCtx, arguments);
}
}(p);
}
this.replay=function() {
for (var i=0; i<this.commands.length; i+=2) {
var com = this.commands[i];
var args = this.commands[i+1];
Context2dPrototype[com].apply(hostedCtx, args);
}
}
}
Obviously you might need some other method (start/stop recording, clear, ...)
Just a small example of use :
var cv = document.getElementById('cv');
var ctx=cv.getContext('2d');
var watchedContext=new WatchedContext(ctx);
// do some drawings on the watched context
// --> they are performed also on the real context
watchedContext.beginPath();
watchedContext.moveTo(10, 10);
watchedContext.lineTo(100, 100);
watchedContext.stroke();
// clear context (not using the watched context to avoid recording)
ctx.clearRect(0,0,100,1000);
// replay what was recorded
watchedContext.replay();
You can see here :
http://jsbin.com/gehixavebe/2/edit?js,output
That the replay does work, and the line is re-drawn as a result of replaying the stored commands.
For storing offline you can either store the commands locally using localStorage or store them remotely on a server an use AJAX calls or similar.
Ive been trying to make the canvas save between page refreshes using html5 local storage, but the canvas always gets back to blank when i refresh the page.
HTML
<canvas onload="loadCanvas()" onClick="canvas(event)" id="myCanvas" width="400" height="400">
Your browser does not support the canvas element
</canvas>
Javascript:
function saveCanvas() {
var c = document.getElementById("myCanvas"),
ctx = c.toDataURL();
if (typeof (localStorage) !== "undefined") {
localStorage.setItem('myCanvas', ctx);
} else {
document.getElementById("save").innerHTML.dataURL = "Local Storage not supported";
}
}
function loadCanvas() {
var image = localStorage.getItem('myCanvas');
document.getElementById('myCanvas').src = image;
}
saveCanvas function gets called when something has been drawed in the canvas
Anyone knows what the problem is?
It has been solved, onLoad did not work in canvas
localStorage can only save so much, ie. in most browsers 5 mb and in others less.
Another caveat is that each char stored takes 2 bytes due to unicoding so the storage is in reality only half of this in the practical sense. There is no guarantee about size as this is not defined by the standard - 5 mb is only a suggestion so browsers can use any size.
You are getting the image as a PNG as this is the default format of toDataURL(). If the produced data-uri is too large (which is likely here as base-64 adds 33% to the size + a small header) the save will truncate or fail depending on the browser.
This is most likely (as you don't state size of canvas or the resulting data-uri) why your canvas is blank when you try to reload the data-uri as it would be invalid.
You can try to save as JPEG instead:
dataUri = c.toDataURL('image/jpeg', 0.5); /// 0.5 is quality, higher is better
If this does not work then you will need to look into other local storage mechanisms such as Indexed DB (where you can request a storage quota) or File API (but this is only supported in Chrome at this moment). There is also the deprecated Web SQL which will be around for still a while.
Update
Also try to move your onload from canvas element to window:
window.onload = function() {
var image = localStorage.getItem('myCanvas');
document.getElementById('myCanvas').src = image;
}
Note: you cannot set a src on a canvas element (as the ID from your code here suggest as well as your example code show). You need an image element for that. When you set a src on an image you also need to use the onload handler on the image, so an example could be:
window.onload = function() {
var img = new Image;
img.onload = function() {
var ctx = document.getElementById('myCanvas').getContext('2d');
ctx.drawImage(img, 0, 0);
/// call next step here...
}
img.src = localStorage.getItem('myCanvas');
}
Usually I suggest (and others did too in this thread) people store their drawings as points and shape types in an array as objects which then is serialized to a string which you instead store in localStorage. It involves a little bit more code in the render stage (which you need anyways to update canvas when it is blanked for some reason) but is worth it.
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.
So far my program is working the way I want it to. This works fine:
// Player object
var player = {
x: 10,
y: 10,
draw: function () {
ctx.drawImage(playerImg, 0, 0);
...
Should I check if playerImg is loaded first, even though it works correctly so far?
Also, what is the best way to check. I was thinking about putting all the images in an array. Then check with the onLoad function. If they are all loaded then I will start the game loop. Is this a good idea?
Thanks
How image loading works
You need to check if the image is loaded as image loading is asynchronous. You may experience that your code works sometimes without. This is mainly because your image exists in the cache and the browser is able to load it fast enough before the drawImage is called, or the image exists on local disk.
However, new users will need to download the data first and you don't want first-time users to experience errors such as images not showing because they are not finished loading.
As it works asynchronous your code will continue to execute while the image loading takes place in the background. This may cause your code to execute before the image has finished loading. So handling image loading is important
Handling multiple images
You can load all your images first (or those you need to start with) and you can define them using array:
var imageURLs = [url1, url2, url3, ...],
images = [],
count = imageURLs.length;
Then iterate and create the image elements:
for(var i = 0; i < count; i++) {
/// create a new image element
var img = new Image();
/// element is valid so we can push that to stack
images.push(img);
/// set handler and url
img.onload = onloadHandler;
img.src = imageURLs[i];
/// if image is cached IE (surprise!) may not trigger onload
if (img.complete) onloadHandler().bind(img);
}
and in the callback function do the inventory count:
function onloadHandler() {
/// optionally: "this" contains current image just loaded
count--;
if (count === 0) callbackDone();
}
Your callback is the code you want to execute next. Your images will be in the array images in the same order as the imageURLs.
For production you should also incorporate an onerror handler in case something goes wrong.