Javascript Image object re-using reference variable, cancels request? - javascript

Tricky getting the title right on this one.
If I use Javascript image object to track something, the http request is done asynchronously (it's not a blocking call), so I've always been concerned that if I re-use the variable that's a reference to the image object, I might be forcing the request to be cancelled. Here's some code that might illustrate my concern.
var img = new Image();
img.src = URL1; //hit server with request 1
img = new Image(); //re-use the img variable for the next new Image
img.src = URL2; //and now request 2. Would this cancel request 1 ?
I can (and currently do) work around this potential problem by having an array of Image objects and I take the next available one, and wrap around at 10. Something like this:
var imgs=[], index=0;
function send(url) {
imgs[index] = new Image();
imgs[index++].src = url;
if(index>=10)index=0; //wraparound
}
send(URL1);
send(URL2);
I know that when re-using the img object in that first code example, I'm not actually doing anything to that original image object, I'm simply orphaning it - reducing it's refcount to zero and opening it up for garbage collection.
Would I be likely to find (in the first example) that the first request was cancelled/aborted? Or would it always complete, only to find that on completion nobody's interested?

You are doing the correct way. If you want to find out whether your request has been cancel or not, install Firebug for your firefox then open net request tab and observe

Related

Adding img.crossOrigin = "*" interferes with img.complete

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.

Which way to create a canvas pattern from a dataToURL-image string as directly as possible?

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( ... );

javascript number of images

I have a small html file for personal use, with no css or php or web server. I have a folder 'imgs' (in the same folder as the .html) that contains images called image0.png, image1.png, image2.png... (all in sequence, until there is none). How could I make a javascript function that returns the number of images in that folder ?
There isn't a way to immediately get the number of image files using client-side JavaScript, the best you could do is to try retrieving the images one by one until, you get a 404 error.
You can use the onerror method of an img element to detect when a requested image doesn't exist - this method will be called if you request an image that isn't there.
This question might be useful, it contains some example code which you could use.
The general strategy would be to load each file as an image, in order, until a request failed, firing an onerror callback. Each subsequent image fetch is fired as the onload handler of the previous image.
// takes a callback that expects a single argument
function getImageCount(callback) {
imageCounter(0);
// pseduo-recursive function nested in the closure of the outer function
// (nested so `callback` is always accessible without constantly passing it)
function imageCounter(i) {
var img = new Image();
// if this failed, so the final image was the previous one
img.onerror = function() { callback(i); }
// if this succeeded, try the next one
img.onload = function() { imageCounter(i + 1); }
img.src = "image"+i+".png";
}
}
// actually run it, with callback function that gets the count
getImageCount(function(count) {
alert("There are " + count + "images.");
});
Due to restrictive same-origin policy for file: URLs, this won't work on Chrome without the --allow-file-access-from-files command line flag, and it will only work in Firefox if the images are being fetched from the same directory or a subdirectory of the current page.

Binding onload event on image object in jQuery

I have following code on my site:
backgroundImages[bg_img_path_b]=new Image();
backgroundImages[bg_img_path_b].src = bg_img_path_b;
backgroundImages[bg_img_path_b].loaded="loading";
//jQuery(backgroundImages[lastImage]).unbind('load.backgroundImages');
jQuery(backgroundImages[bg_img_path_b]).bind('load.backgroundImages',function(){
if(typeof callback=='function'){
callback.call(this, bg_img_path_b);
if(showLoading) hideLoadingDC();
}
}).bind('load.cache', function(){
backgroundImages[bg_img_path_b].loaded="true";
});;
There is large gallery for images used as background-images of page wrapper. I need to preload images because of speed (the images are quite large). So I have this code (actually is only a part of bigger function, which wraps caching and so on, but this few lines are fired when image is not in cache).
backgroundImages is large array of Image objects, key is the path is the path of image. Every Image object has my property "loaded" which says if image has already been loaded, or is currently in state of loading.
As you can see from my code I am calling callback function when the image is loaded (there are changes of the background etc.)
But I have a problem with I.E<9, the callback is not successfully fired up (but not every time)..When I load my page for first it loads properly, but anytime I call this function again, it goes correctly through without errors, but the load event doesn't fired..
I really don't know where could be an error, in all browsers except older IEs it works fine..
Actually I need to debug if the event is bound correctly, but I can't see it in both IE and Chrome under load item in debugger:(
Please help I am completely screwed, really don't know what to do.
So few days ago I finally found out a solution to my problem..It seems its matter if I at first bind the onload event and then set the url of Image, or at first set the url and then bind the event..
Example
var photo = new Image();
photo.url = "http://www.something.com/something.jpg";
$(photo).bind('load',doSomething());
var photo = new Image();
$(photo).bind('load',doSomething());
photo.url = "http://www.something.com/something.jpg";
First variant runs usually ok, but sometimes it fails (in older IEs)..But the second variant is sure working propely..
which jQuery API version are you using? Try using the latest version of jQuery.
Otherwise use this simple JavaScript code to load images on calling your function on body onload:
var myimages = new Array();
var path = "images/gallery/";
function preloadimages() {
for (i = 0; i < preloadimages.arguments.length; i++) {
myimages[i]=new Image();
myimages[i].src=path+preloadimages.arguments[i];
}
}
// Enter name of images to be preloaded inside parenthesis with douable quotes.
// Extend list as desired with comma.
preloadimages("gImg1.jpg","gImg2.jpg","gImg3.jpg","gImg4.jpg" /*, etc...*/);

Getting size of javascript preloaded image

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.

Categories

Resources