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!
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.
I am attempting to use a chrome extension to take a screenshot of the current page, and then draw some shapes on it. After I have done that to the image, I turn the whole thing into a canvas that way it is all together, like the divs I have drawn on it are now baked into the 'image', and they are one in the same. After doing this, I want to turn the canvas back into a png image I can use to push to a service I have, but when I go to use the canvas.toDataURL() in order to do so, the image source that it creates is completely transparent. If I do it as a jpeg, it is completely black.
I read something about the canvas being 'dirtied' because I have drawn an image to it, and that this won't work in Chrome, but that doesn't make sense to me as I have gotten it to work before, but I am unable to use my previous method. Below is the code snippet that isn't working. I am just making a canvas element, and then I am drawing an image before that.
var passes = rectangles.length;
var run = 0;
var context = hiDefCanvas.getContext('2d');
while (run < passes) {
var rect = rectangles[run];
// Set the stroke and fill color
context.strokeStyle = 'rgba(0,255,130,0.7)';
context.fillStyle = 'rgba(0,0,255,0.1)';
context.rect(rect.left, rect.top, rect.width, rect.height);
context.setLineDash([2,1]);
context.lineWidth = 2;
run++;
} // end of the while loop
screencapImage.className = 'hide';
context.fill();
context.stroke();
console.log(hiDefCanvas.toDataURL());
And the image data that it returns is: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAACWAAAAVGCAYAAAAaGIAxAAAgAElEQ…ECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQBVKBUe32pNYAAAAAElFTkSuQmCC which is a blank, transparent image.
Is there something special I need to do with Chrome? Is there something that I am missing? Thanks, I appreciate the time and help.
Had the same problem, and found the solution here:
https://bugzilla.mozilla.org/show_bug.cgi?id=749824
"I can confirm, that it works if you set preserveDrawingBuffer.
var glContextAttributes = { preserveDrawingBuffer: true };
var gl = canvas.getContext("experimental-webgl", glContextAttributes);"
After getting the context with preserveDrawingBuffer, toDataURL just works as expected, no completely transparent or black image.
Having a similar problem I found a solution, thanks to the following post
https://github.com/iddan/react-native-canvas/issues/29.
These methods return a promise, so you would need to wait for it to resolve before the variable can be populated.
My solution was to set asyn function and await for the result:
const canvas = <HTMLCanvasElement>document.getElementById("myCanvas")[0];
let ctx = canvas.getContext('2d');
let img = new Image();
img.onload = async (e) => { ctx.drawImage(img, 0, 0); ctx.font = "165px Arial";ctx.fillStyle = "white"; b64Code = await (<any>canvas).toDataURL(); }
Got a image inside of a HTML document. Source is a base64 string. I want to retrieve the color of the pixel that is clicked on. Using a memory canvas all I get is zeros.
function getColor(imagecontainer,top,left){
var image = new Image();
image.onload = function() {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
var myData = context.getImageData(Math.round(left)-2, Math.round(top)-2, 4, 4).data;
console.log(myData, left, top);
};
image.src = imagecontainer.find("img").attr("src");
}
There are many other questions regarding the same problem, however none of the solutions could solve this problem for me. MyData always contains zeros.
Update based on new information:
The cause could simply be that the main image (imagecontainer.find("img")) isn't loaded at the time it is references.
If this image exists in DOM you can use windows.onload to run your script:
window.onload = function() {
// code that uses the image
};
as this will run only when everything has loaded incl. image data. Optionally add an inline onload handler to the image tag (not recommended), or add the src via JavaScript and monitor the onload event there.
Another possible cause is that if this is IE and the image is in the cache, onload may not trigger. You can check the image's complete property to check for this:
var image = new Image();
image.onload = onloadHandler;
image.src = imagecontainer.find("img").attr("src");
if (image.complete) onloadHandler();
function onloadHandler() {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
var myData = context.getImageData(Math.round(left)-2, Math.round(top)-2, 4, 4).data;
};
Although you can use this directly with the original image without loading another with the same url.
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.
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.