I have a function that reads map tile images. I want to keep track of whether or not a certain image has already been cached. I'm using this function from this thread:
function is_cached(src) {
var image = new Image();
image.src = src;
return image.complete;
}
This was working great. But then I needed to do some image processing. In order to copy the image data to a canvas and process it pixel by pixel, I need to use CanvasRenderingContext2D.drawImage(image, 0, 0). But it bugs me with a cross-origin error. So I can add a image.crossOrigin = "*", which solves that problem, and I can write to a canvas and do the image processing I need. That bit looks like this:
imageOutput.crossOrigin = "*"
var demCtx;
imageOutput.onload = function(){
var c = document.createElement('canvas')
c.width = c.height = 256
demCtx = c.getContext('2d')
demCtx.drawImage(imageOutput, 0, 0)
var imageData = demCtx.getImageData(0, 0, 256, 256)
}
The issue that arises is that every time I run the larger function which contains these two bits of code, the is_cached function returns false every time, except the first time. But I know that even though is_cached is returning false, the images are indeed cached, as they are loading with 0 lag (as opposed to when a novel image is called and it takes a moment to grab it from the server).
Why might .crossOrigin = "*" be interfering with the .complete status of an image?
This is happening within an ObservableHQ notebook. Might that have something to do with it? ObservaleHQ gets weird sometimes.
ObservableHQ Notebook with the problem
You can find this code in the getTileUrl cell at the bottom. This notebook is not yet finished. You can see the cached status at the Tile Previously Cached line after you click around the map of submit changes to the inputs.
Thanks for reading.
Maybe fetch api can enforce cache using the param {cache:"force-cache"}, however images should be cached as expected. You can fetch the image and pass its blob as an image source.
replace your imageOutput.src with
imageOutput.src = URL.createObjectURL(await fetch(imageUrl, {cache:"force-cache"}).then(r => r.blob()));
make your getTileURL function async as we have to await fetch and blob to be ready to be passed as image source
async function getTileURL(latArg, lngArg, zoomArg) {
Use devtools to inspect network and see tile images coming from disk cache
edit:
just try your original code and inspect network via devtools. The tiles images are cache as expected. So no need to hack into fetch blob src.
Related
Task
I am currently trying to create a web extension for Firefox.
It should be able to read images' pixels.
For that purpose, I am rendering the image on an invsible canvas, and then I want to read it.
Example Code
function getImdata(reference) {
var canvas=document.createElement("canvas");
canvas.width=reference.naturalWidth;
canvas.height=reference.naturalHeight;
var context=canvas.getContext("2d");
context.drawImage(reference,0,0);
return context.getImageData(0,0,reference.naturalWidth,reference.naturalHeight); //Here I actually get the error...
}
Problem
However, I am getting a "Security Error" if I use "getImageData()".
Question
So I need a workaround, but couldn't find anything myself.
How can I read images' pixels without getImageData() ?
EDIT
Apparently it has something to do with CORS : HTML5 Canvas getImageData and Same Origin Policy
Thanks in advance!
There is. Since you're running from an extension your extension will have privileged access to cross-origin sources but only if loaded via fetch() and XMLHttpRequest() from a content script (or background script) - excerpt from that link:
Content scripts get the same cross-domain privileges as the rest of
the extension: so if the extension has requested cross-domain access
for a domain using the permissions key in manifest.json, then its
content scripts get access that domain as well.
This is accomplished by exposing more privileged XHR and fetch
instances in the content script [...]
Please note that these calls when called from a content script will not set origin and referer headers which sometimes can cause problems if the cross-origin site expects these to be set - for those cases you will need to use the non-privileged content.XMLHttpRequest or content.fetch() which will bring you back to square one.
The permissions in the manifest file (or if set permissions dynamically) must also allow access to these cross-origin sites.
This means however that you will have to "reload" the image source separately via these calls. You can do this the following way by first obtaining the original URL to the image you want to load, say, from a content script:
// example loading all images in current tab
let images = document.querySelectorAll("img");
for(let image of images) loadAsBitmap(image.src); // some sub-call using the url
Then load that source via the content script's fetch():
fetch(src).then(resp => { // load from original source
return resp.blob(); // obtain a blob
}).then(blob => { // convert blob, see below
// ...
};
When the blob is obtained you can convert it to an Object-URL and set that as source for an image and be able to go around the cross-origin restriction we otherwise face. In the content script, next steps would be:
let url = URL.createObjectURL(blob); // attach Object-URL to blob
let img = new Image(); // create image element *
img.onload = () => { // attach handler
let c = document.createElement("canvas"); // create canvas
let ctx = c.getContext("2d"); // get context
c.width = img.width; // canvas size = image
c.height = img.height;
ctx.drawImage(img, 0, 0); // draw in image
URL.revokeObjectURL(url); // remove reference.
let imageData =
ctx.getImageData(0,0,c.width,c.height); // get image data
// .. callback to a function that handles the image data
};
img.src = url; // start loading blob
I just started to learn a little bit of JavaScript and i wondered if there is a way to check if an image is already loaded to cache.
In my script im loading some random images from another webpage and display them.
When the same image will be displayed the second time, the script won't use the already loaded image, but instead load the same image again, so that there are two of them stored in my cache.
Now I want to check if the image is already stored in cache and if so, use the cached one instead of loading it again.
My code:
<script>
var img = document.createElement('img');
var index;
//On Click create random 3digit number between 1 and 100
document.getElementById('image').onclick = function(){
var index = '' + Math.floor(Math.random() * 100 +1);
while(index.length < 3) {
index = '0' + index;
}
loadImages(index);
};
//Load the image with the created number
function loadImages(id) {
var src = 'someWebPage/' + id +'.png';
img.onload = function () {
document.getElementById('image').getContext("2d").drawImage(img, 0, 0, 300, 300);
}
img.src = src;
}
</script>
Picture of my cache:
As you can see 030.png and 032.png are twice in cache.
Hope you can give me some advice.
EDIT:
Just for anyone else that faces this problem, it actually isnĀ“t one at all.
Chrome already did everything right, i only did not notice.
As you can see in the column Size the pictures were already loaded from my cache.
The way caching (in this context) is handled is by the browser negotiating with the server using a set of headers to basically tell the server "I already have this version of this resource", to which the server can then respond "OK, that is still valid, no need to download anything new". So you shouldn't be concerned about the caching in the JavaScript side, but instead make sure you are setting the correct Cache-Control headers on the server side. There are likely already questions/answers for your server/framework of choice on how to setup the caching there.
I'm using an image that I much previously had made by
var patternImageAsDataURL= canvasObject.toDataURL('image/png');
In a later stage I want to make a canvas pattern object. The following code doesn't work - I assume the image is simply not loaded when going to the last line, where it is needed in the createPattern function.
var img = document.createElement('img');
img.src = patternImageAsDataURL;
// canvasctx was created somewhere else in the program
pattern = canvasctx.createPattern(img,'repeat');
I get the error: NS_ERROR_NOT_AVAILABLE: on the last line. (And when using console.log on width and heigth of img between the two last lines, I see when it's not working the dimensions are 0.)
When later on the same operation is done with the same dataURL, it does work. Though the image (img) should always be created anew. (Only reason I can see it's because of some internal optimization in Firefox. But that's offtopic here, unless someone does know the answer.) The width and height when printing them out to the console are correct then.
While I will quite soon program some pattern handling service, that should solve this, my question is in general and for speed concerns and for simplicity. (If I use some code with like 20 to 50 objects with patterns, I would prefer a lean solution over a memory or time saving function.)
Could I somehow use the dataURL more directly (and faster) for the
createPattern function?
And:
Could I force the program to wait after the img.src = patternImageAsDataURL; command until the image is loaded, and then to go on processing the code? (Like in the synchronous mode of the XMLrequests.)
(Using the onload event of the image isn't feasible in the current program flow.)
This is running on Firefox 32, Win 7.
A faster, more direct way to create a pattern
You can use a second canvas element as the source for a pattern.
This allows you to completely skip the interim step of creating an ImageURL and Image from your source canvas so your pattern creation will be faster.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// Make a temporary canvas to be the template for a pattern
var pc=document.createElement('canvas');
var px=pc.getContext('2d');
pc.width=4;
pc.height=4;
px.fillStyle='palegreen';
px.fillRect(0,0,2,2);
px.fillRect(2,2,2,2);
// Use the temporary canvas as the image source for "createPattern"
var pattern=ctx.createPattern(pc,'repeat');
ctx.fillStyle=pattern;
ctx.fillRect(50,50,100,75);
ctx.strokeRect(50,50,100,75);
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<h4>Using a temporary canvas as source for a Pattern.</h4>
<canvas id="canvas" width=300 height=300></canvas>
Option 1 - Canvas as image source
The obvious is of course to use the canvas itself as image source for the pattern.
createPattern() can take image, canvas, context (although not all browsers allow this) or even video as source.
CanvasPattern createPattern(CanvasImageSource image,
[TreatNullAs=EmptyString] DOMString repetition);
where CanvasImageSource is defined as:
typedef (HTMLImageElement or
HTMLVideoElement or
HTMLCanvasElement or
CanvasRenderingContext2D or
ImageBitmap) CanvasImageSource;
This is also the only way that will allow you to not use onload at some point later (provided the pattern is generated and not drawn in from an image/video source).
You cannot deal with asynchronous behavior without using callbacks (or promises), and expect the program to work properly. Period.
Option 2 - Data-URIs
If you for some reason cannot use the original canvas as source, you have to deal with the image asynchronously. Add a onload handler for it and continue from inside it:
var img = document.createElement('img');
img.onload = function() {
pattern = canvasctx.createPattern(this, 'repeat');
// continue from here..
};
img.src = patternImageAsDataURL;
Note that the process of this is relative slow due to the additional encoding/decoding process on top of the image handling itself. You can find more details about this in this answer.
Option 3 - Blob and object-URL
A Blob lets you store the data in binary form. This is preferred over storing the binary data as encoded string as with data-URIs. This will be faster to embed as well as extract compared to data-URIs.
You can use URL form with the Blob and use that as image source.
First create the Blob directly from canvas:
var patternImageAsBlob = canvas.toBlob(...); //IE: msToBlob()
This is also an asynchronous call so you need to take that into account.
For example:
var patternAsBlob;
canvas.toBlob(function(blob) {
patternAsBlob = blob;
// continue from here
}
Then when you need it as an image, generate an Object-URL for it like this:
var img = new Image(),
url = URL.createObjectURL(patternAsBlob);
img.onload = function() {
URL.revokeObjectURL(url); // clean up by removing the url object
pattern = canvasctx.createPattern(this, 'repeat');
// continue from here..
};
img.src = url;
Tips
If you have several images to load and set, it would be better to make an image loader to load in all resources to an array, when done create the patterns.
This will simplify the asynchronous chain-calling (optionally use promises, but this is not yet supported in IE without a polyfill).
You may need a polyfill for toBlob in older browser. One can be found here.
You may need to "unprefix" the createObjectURL(), here is one way:
var domURL = self.URL || self.webkitURL || self;
var url = domURL.createObjectURL( ... );
I have created a webpage that receives base64 encoded bitmaps over a Websocket and then draws them to a canvas. It works perfectly. Except, the browser's (whether Firefox, Chrome, or Safari) memory usage increases with each image and never goes down. So, there must be a memory leak in my code or some other bug. If I comment out the call to context.drawImage, the memory leak does not occur (but then of course the image is never drawn). Below are snippets from my webpage. Any help is appreciated. Thanks!
// global variables
var canvas;
var context;
...
ws.onmessage = function(evt)
{
var received_msg = evt.data;
var display_image = new Image();
display_image.onload = function ()
{
context.drawImage(this, 0, 0);
}
display_image.src = 'data:image/bmp;base64,'+received_msg;
}
...
canvas=document.getElementById('ImageCanvas');
context=canvas.getContext('2d');
...
<canvas id="ImageCanvas" width="430" height="330"></canvas>
UPDATE 12/19/2011
I can work around this problem by dynamically creating/destroying the canvas every 100 images or so with createElement/appendChild and removeChild. After that, I have no more memory problems with Firefox and Chrome.
However, Safari still has a memory usage problem, but I think it is a different problem, unrelated to Canvas. There seems to be an issue with repeatedly changing the "src" of the image in Safari, as if it will never free this memory.
display_image.src = 'data:image/bmp;base64,'+received_msg;
This is the same problem described on the following site: http://waldheinz.de/2010/06/webkit-leaks-data-uris/
UPDATE 12/21/2011
I was hoping to get around this Safari problem by converting my received base64 string to a blob (with a "dataURItoBlob" function that I found on this site) and back to a URL with window.URL.createObjectURL, setting my image src to this URL, and then later freeing the memory by calling window.URL.revokeObjectURL. I got this all working, and Chrome and Firefox display the images correctly. Unfortunately, Safari does not appear to have support for BlobBuilder, so it is not a solution I can use. This is strange, since many places including the O'Reilly "Programming HTML5 Applications" book state that BlobBuilder is supported in Safari/WebKit Nightly Builds. I downloaded the latest Windows nightly build from http://nightly.webkit.org/ and ran WebKit.exe but BlobBuilder and WebKitBlobBuilder are still undefined.
UPDATE 01/03/2012
Ok, I finally fixed this by decoding the base64-encoded data URI string with atob() and then creating a pixel data array and writing it to the canvas with putImageData (see http://beej.us/blog/2010/02/html5s-canvas-part-ii-pixel-manipulation/). Doing it this way (as opposed to constantly modifying an image's "src" and calling drawImage in the onload function), I no longer see a memory leak in Safari or any browser.
Without actual working code we can only speculate as to why.
If you're sending the same image over and over you're making a new image every time. This is bad. You'd want to do something like this:
var images = {}; // a map of all the images
ws.onmessage = function(evt)
{
var received_msg = evt.data;
var display_image;
var src = 'data:image/bmp;base64,'+received_msg;
// We've got two distinct scenarios here for images coming over the line:
if (images[src] !== undefined) {
// Image has come over before and therefore already been created,
// so don't make a new one!
display_image = images[src];
display_image.onload = function () {
context.drawImage(this, 0, 0);
}
} else {
// Never before seen image, make a new Image()
display_image = new Image();
display_image.onload = function () {
context.drawImage(this, 0, 0);
}
display_image.src = src;
images[src] = display_image; // save it for reuse
}
}
There are more efficient ways to write that (I'm duplicating onload code for instance, and I am not checking to see if an image is already complete). I'll leave those parts up to you though, you get the idea.
you're probably drawing the image a lot more times than you are expecting to. try adding a counter and output the number to an alert or to a div in the page to see how many times the image is being drawn.
That's very interesting. This is worth reporting as a bug to the various browser vendors (my feeling is that it shouldn't happen). You might responses along the lines of "Don't do that, instead do such and such" but at least then you'll know the right answer and have an interesting thing to write up for a blog post (more people will definitely run into this issue).
One thing to try is unsetting the image src (and onload handler) right after the call to drawImage. It might not free up all the memory but it might get most of it back.
If that doesn't work, you could always create a pool of image objects and re-use them once they have drawn to the canvas. That's a hassle because you'll have to track the state of those objects and also set your pool to an appropriate size (or make it grow/shrink based on traffic).
Please report back your results. I'm very interested because I use a similar technique for one of the tightPNG encoding in noVNC (and I'm sure others will be interested too).
I don't believe this is a bug. The problem seems to be that the images are stacked on top of each other. So to clear up the memory, you need to use clearRect() to clear your canvas before drawing the new image in it.
ctx.clearRect(0, 0, canvas.width, canvas.height);
How to clear your canvas matters
I'm trying to preload a number of images generated on the server to a small website. The preloading is done using setWindowTimeout and uses an Image object, sets the onload callback and then applies the new request uri.
For some requests, the server may have to signal that the image is 'unchanged' and I'm doing it by sending done a small 1x1 pixel gif (seems like I need to send an actual image, returning empty content will cause the Image object to not fire onload). In my onload handler I would like to determine the size of the fetched image and then determine if I should update the visual image with the given image.
Below is a snippet of my current solution (the output is a div to help debug):
refresh: function() {
var newSrc = '/screenshot.ashx?tick=' + new Date().getTime();
var imgObj = new Image();
var self = this;
// this is called when load is done
imgObj.onload = function() {
//if (imgObj.complete)
// return;
if (imgObj.width > 1 && imgObj.height > 1) {
output.innerHTML += '<br/>Updating image:' + newSrc;
img.src = newSrc; //fiddler shows no reload here, read from cache
}
else {
output.innerHTML += '<br/>Empty image:' + newSrc;
}
self.setupNewRefresh();
};
output.innerHTML += '<br/>Loading image:' + newSrc;
imgObj.src = newSrc;
},
I seem to have two problems:
a) the imgObj.complete is false when the function is first called but it is only called once (hence my commenting it out) and
b) I can't seem to rely on the width or height property of the image when loaded. From my tests of fetch the 1x1 pixel, it sometimes reads out 50 which seems to be default when creating a new Image() and it sometimes reads out the correct size of 1.
My ultimate goal is to have a small chunk of javascript logic that queries the server for a new image periodically, does nothing if nothing new has happened (1 pixel image) or loads the new image. I might be going about it the wrong way here or have overlooked important properties or calls, so I'm happy to receive feedbacks.
EDIT: upon suggestion from mplungjan, I preloaded into a hidden div instead, which helped me with problem b). Still no solution to problem a) though; reports complete = false once and is not called again
I'll go ahead and answer my own question.
Preloading the image into a hidden element in the DOM instead of a code-level element was the trick for actually getting correct sizes (thanks mplungjan). The a) issue, namely the complete event, remains unsolved.
As a side note I ended up using the XMLHttpRequest instead as it allowed by to look at the size of the payload returned.