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.
Related
Now my entire project is too big for a copy and paste, but here is a basic breakdown of what I'm doing right now:
$(document).ready(function(){
var canvas = $('#Canvas');
var data = {source: "images\pattern.png", repeat: "repeat"} ;
var pattern = canvas.createPattern(data); //Returns 'Null' at random
});
Things I've looked at so far:
Loading time (Somewhat related, it seems to break more often when the page loads faster)
Loading order (Doesn't seem related)
Preloading the image by forcing it to an on-page image beforehand (Doesn't fix it )
Preloading it using Image() and passing the image path to Image().src (doesn't fix it either)
Passing the Image() as data.source instead of a string path (might have helped just a little)
Initting JCanvas beforehand (No function seems to exists for this)
Creating the pattern as early or as late as possible (Seems to change the frequency, but not by a lot)
It seems to have a mind of it's own and I can't figure out what I'm doing wrong. Anyone have a clue as to what I'm doing wrong?
-Edit1-
Debugging through the Jcanvas source right now and I think it has something to do with the context. Is there any way for me to preload the canvas context?
-Edit2-
Forget everything I've said about the context, I think I figured it out.
//JCanvas source
[...]
else {
// Use URL if given to get the image
img = new Image();
img.crossOrigin = params.crossOrigin;
img.src = source; //<-- source is the url of my image ("images\pattern.png")
}
// Create pattern if already loaded
if (img.complete || imgCtx) {
onload(); //<-- When this runs, the image pops up perfectly fine
} else {
img.onload = onload(); //<-- This is what causes the problem,
//onload never seems to actually run
// Fix onload() bug in IE9
img.src = img.src;
}
The img.onload event should happen directly after an image loads, but it never seems to happen.
I made a jsFiddle which demonstrates pixel manipulation in JavaScript. It works perfectly fine in Chrome. Then I moved to test it on Firefox.
It doesn't work and it threw an error:
IndexSizeError: Index or size is negative or greater than the allowed amount
This confuses me. But wait, there's more.
When I click Run again, the code suddenly works. I don't know what sorcery is this, or it's just some weird Firefox bug.
You can see the problem here: http://jsfiddle.net/DerekL/RdK7H/
When you are in jsFiddle, click on Select File and select a PNG file. You should see the code is not working. Then you click Run. Do the same thing again, and it suddenly works.
There are also some problems in some of the functions in Firefox which also frustrated me, however it is not part of this question.
If you need to know, I'm using Firefox 26.0.
It is because your image hasn't completed loading yet so the default width and height of the image is returned (both 0). As you cannot use 0 for the width and height of getImageData() you get an error.
When I click Run again, the code suddenly works. I don't know what
sorcery is this, or it's just some weird Firefox bug.
It's because the image is now in the cache and the browser happens to be able to provide it before you attempt to read its width and height (no, the bug is in your code :-) ).
Handling image loading with a busy loop and a timeout value is begging to fail.
Make sure you add an onload handler to the image (this may require you to refactor the code a bit to support a callback (or promise) and the return value won't be valid for the same reason as the error):
getRGBArray: function(uri, callback){ /// add parameter for callback here
var image = new Image();
image.onload = imageLoaded; /// add an onload handler here
image.src = uri;
function imageLoaded() {
//var t = Date.now();
//while(Date.now() - t < 3000 && !image.width);
var width = this.width, /// replace image with this to be sure you
height = this.height, /// ..are dealing with the correct image in
canvas = $("<canvas>").attr({ /// ..case you load several ones..
width: width,
height: height
}).appendTo("body"),
ctx = canvas[0].getContext("2d");
ctx.drawImage(this, 0, 0);
var imgData = ctx.getImageData(0, 0, width, height).data;
...
callback(imgData); /// example of callback
}
...
}
Optionally separate the image loading so you can call this function without relying on if the image has loaded or not.
Update
As briefly mentioned you can separate the image loading from the your main code. For example - instead of loading your image in the getRGBArray() function, pre-load it somewhere else in the code and pass the image as an argument instead (callback cannot be avoided but you can keep your original code synchronous after the loading point):
function loadImage(url, callback) {
var image = new Image();
image.onload = function() {
callback(this);
}
image.src = uri;
}
Then call it for example like this:
loadImage(url, readyToGo);
function readyToGo(image) {
var pixels = getRGBArray(image);
...
}
A small modification in the original function to make it use the passed image instead of url:
getRGBArray: function(image){
var width = image.width,
height = image.height,
canvas = $("<canvas>").attr({
width: width,
height: height
}).appendTo("body"),
ctx = canvas[0].getContext("2d");
ctx.drawImage(image, 0, 0);
var imgData = ctx.getImageData(0, 0, width, height).data;
...
return opt;
}
...
}
Hope this helps!
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.
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.
The idea of my code is create a hidden div which loads the image. When it's load event is fired draw it in the canvas. When I run the code I get this error 0x80040111 (NS_ERROR_NOT_AVAILABLE), yet I am waiting for the load event. Here is my code.
HTML
<div id="old-counties-image-wrapper" style="display: none;">
<img border="0" height="390" id="interreg-iiia-old-counties-map" src="/f/MISCELLANEOUS/old-map.jpg" /></div>
<p>
<canvas id="old-counties-image-canvas"></canvas></p>
and javascript
$('#interreg-iiia-old-counties-map').load(function() {
var canvas=document.getElementById('old-counties-image-canvas');
if (canvas.getContext) {
var ctx=canvas.getContext('2d');
var img=$('#interreg-iiia-old-counties-map');
ctx.drawImage(img, 0, 0);
}
//else {
// $('#old-counties-image-wrapper').show();
//}
});
The else part is commented out for now but is there for browsers that don't support canvas.
Because $('#interreg-iiia-old-counties-map') returns a jQuery object, while the drawImage method takes an Image object - the jQuery ($) function returns a jQuery object that wraps the original element to provide the usual jQuery functions you see.
You can get the underlying Image object by using the get method, but in this case it would be easier to just use this, which in the context of the callback function supplied to the load function, is the original $('#interreg-iiia-old-counties-map') DOM element. In other words,
ctx.drawImage(this, 0, 0);
should work fine here. You also don't have to use a hidden <img> element - with new Image you can retrieve the image similar to what you're doing here:
var img = new Image(),
canvas = document.getElementById('old-counties-image-canvas');
img.src = '/f/MISCELLANEOUS/old-map.jpg';
img.onload = function(){
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
}
};
And, now for my own take on a solution:
var imgId = $("#myDiv img").attr("id");
var imgObj = document.getElementById(imgId);
var canvasContext = $("#imgCanvas")[0].getContext('2d');
canvasContext.drawImage(imgObj, 0, 0);
It's ugly, but I don't think it's much uglier than the solutions presented above. There may be performance advantages to other solutions as well, though this one seems to avoid needing to load the image file from the server's file system, which should be worth something. I tested it in Chrome, Firefox, and IE10.