How to load an other image, if the current image loads slow? - javascript

I'm working on a HTML5 canvas game, and I want to load a high resolution image as background. This image is 10MB, so if the image loads slow, then I want to load a smaller image instead.
var myimg = new Image();
myimg.onload = function() {
clearTimeout(t);
alert('high resolution image loaded');
};
mximg.src = 'path/to/highres.png';
var t = setTimeout(function(){
//cancel loading somehow ?
myimg.onload = function() {
alert('low resolution image loaded');
};
myimg.src = 'path/to/low_res.png';
},5000);
should I cancel somehow the loading, or it's cancelled if I edit the src attribute?

One alternative "solution" would be to load the low-res image first. Only if it loaded quickly enough you start to load the high-res version to replace it.
There are three possible benefits as I see it:
You avoid having to worry about cancelling the load :-)
You avoid loading unnecessary data (the partially downloaded high-res image) when the user is on low bandwidth, but instead load unnecessary data (the complete low-res image) when the user is on high bandwidth (in which case it doesn't matter that much anyway).
While you load the high-res image, you have the option to show the low-res image as a placeholder (requires that you load the low-res and high-res images into two separarate DOM Image objects and swap out the low-res with the high-res).

You can't stop the loading of images, however it is possible to cancel an ordinary AJAX request (see this solution on SO). Usually images aren't loaded via AJAX for good reasons, but you can work around this by sending the image as a base64 encoded string. So you let PHP read the image and convert it to base64. This string can be sent to JS via AJAX and "directly" drawn to your canvas (solution on SO).
I really don't know if this is the best way to do this... Anyways, see if this works for you:
HTML:
<canvas id="background" width="800" height="600"></canvas>
JavaScript: (using jQuery)
function drawBackgroundImage(src) {
var image = $("<img/>").load(function(){
$("canvas#background")[0].getContext("2d").drawImage(image[0], 0, 0);
}).attr("src", src);
}
function loadImage(highres, lowres) {
var t;
var xhr = $.ajax({
type: "POST",
url: "load_image.php",
data: "image=" + highres,
success: function(imgbinary){
if (imgbinary != "") {
clearTimeout(t);
drawBackgroundImage(imgbinary);
}
}
});
t = setTimeout(function(){
xhr.abort();
drawBackgroundImage(lowres);
}, 5000);
}
loadImage("path/to/highres.png", "path/to/low_res.png");
PHP: (load_image.php)
(This uses part of a function from the PHP manual.)
<?php
$result = "";
#ob_start();
if (isset($_POST["image"])) {
$filename = $_POST["image"];
if (file_exists($filename)) {
$filetype = pathinfo($filename, PATHINFO_EXTENSION);
$imgbinary = fread(fopen($filename, "r"), filesize($filename));
$result = "data:image/".$filetype.";base64,".base64_encode($imgbinary);
}
}
#ob_end_clean();
echo $result;
?>

Related

Have trouble with processing image in web page

I have been trying to upload an image and convert it into grayscale version then display both images on a webpage. I have test and verified the working of the javascript code locally and it works. However when I integrate it into my webpage only the orignal image is being uploaded. The greyscale image is not being generated or the function is not being processed.
function doUpload(){
var image = new SimpleImage(inputFile);
image.drawTo(CanvasOG);
var imageGS = grayScale(image);
imageGS.drawTo(CanvasGS);
//var NewImage = grayScale(image);
//NewImage.drawTo(CanvasGS)
}
Can you tell me what went wrong?
PS Im using the Duke university's learn to program course's SimpleImage library for the functions to read the images and pixel values
Im posting a link to the codepen page incase you want to see the entire code with the html page
https://codepen.io/girish-kumar-peddi/pen/PoPBxKb
The nature of SimpleImage.js drawTo() contains a setTimeout(). Thus the imageData is not set instantly, but after 100ms. So after the image is drawn to the canvas, wait sometime before manipulating the image data. Enclosed the drawTo() implementation below.
// Draws to the given canvas, setting its size to match SimpleImage's size
drawTo: function (toCanvas) {
if (this.imageData != null) {
__SimpleImageUtilities.flush(this.context, this.imageData);
toCanvas.width = this.getWidth();
toCanvas.height = this.getHeight();
toCanvas.getContext('2d').drawImage(this.canvas, 0, 0, toCanvas.width, toCanvas.height);
}
else {
var myself = this;
setTimeout(function() {
myself.drawTo(toCanvas);
}, 100);
}
},

Can not load images from cache

Trying to build a gallery with thumbnails.
You click a thumbnail, the main image container will show it.
It seems that when I change the URL of the container, it will ALWAYS reload the image from the Internet and not cache it.
I tried 2 approaches both have the same result.
First :
//save to cache right when script is loaded
var cacheImages =[];
preloadimgs();
function preloadimgs(){
for(var k=0;k<5;k++) {
var img = new Image();
img.src = currentStore.product.media.photos[k];
cacheImages.push(img);
}
}
//load
function thumb(arg){
var prvimgShow = cacheImages[arg].src;
var finalurl="url("+prvimgShow+")";
$("#preview").stop().animate({opacity: 0.7},100,function(){
$(this).css({'background-image': finalurl})
.animate({opacity: 1},{duration:200});
});
}
Which will result in a VERY slow reloading of that photo whenever I click a thumb.
Second(which I asked about here already):
Here I get the photo FROM the thumbnail background as a source :
//get a thumb's background photo
var prvimgShow = document.getElementById("thumb0").style.backgroundImage;
//show it
$('#thumb0').on('click', () => {
$("#preview").stop().animate({
opacity: 0.7
}, 100, function() {
$(this).css({
'background-image': prvimgShow
})
.animate({
opacity: 1
}, {
duration: 200
});
});
})
BOTH RESULT IN RELOADING OF THE IMAGE WHICH TAKES 2 SECONDS.
The images are very small (300k), and for a gallery this is a bad UX.
While I don't know internals of browser and why it is not caching, One of the solution I can think of is:
Don't use the direct url and instead download the file and make it into a blob url. Keep the reference of the blob url instead in cacheImages just as you are doing. Then use the blog url else where and resource will not be downloaded again.
You can refer How to get a File() or Blob() from an URL in javascript? for how to fetch image and convert it to a blob url.
If you want the browser to load the images from the cache, you have to configure this on the server. You can use Etag-Header or Cache-Control Header. This will let the browser know that it can load these assets from the cache.

Preload images asynchronously into the browser cache when the image has to be generated on the server first

been reading stackoverflow for a couple years now, but never posted. Until today - ran into an issue I could not solve by myself and did not find a solution for.
The scenario: I have a dynamic webpage which basically shows screenshots of websites. These screenshots are generated on the fly for every new user and their URLs change. I want to preload these images into the browser cache so they're available in 0ms once the user clicks on a link. I don't want the subjective load time of the page increased, so they have to be loaded invisibly in the background.
My approach:
I used jelastic as my infrastructure to be able to scale later, then installed centOS with nginx, PHP and PhantomJS. I use PHP to query phantomJS to make the screenshots:
exec ("phantomjs engine.js ".$source." ".$filez. " > /dev/null &");
The dev/null is used to not increase the load time to the user.
I output the links to the browser. So far it works. Now I want to preload these images:
for (var i = 0; i < document.links.length; i++) {
imgArray[i] = new Image(1,1);
imgArray[i].visibility = 'hidden';
imgArray[i].src = (document.links[i].href.substr(7) + ".png");
document.links[i].href = 'javascript: showtouser("' + imgArray[i].src.substr(7) + '");';
}
Two things I proably did wrong here:
I start the image preloading before the images are generated on the server. I haven't found a way to start the caching only once the image has been generated by phantomJS. Onload event obviously does not work here.
I think my approach is not really async and it would increase the subjective loading time felt by the user
What am I doing wrong? I'm an ISP guy, I suck at javascript :/
You're approach was async. What you needed was a mechanism to identify image that didn't load, and retry.
This script will preload images, retry if failed, and hide links of images that don't exist even after retries (demo):
var links = Array.prototype.slice.call(document.links, 0); // Converting links to a normal array
links.forEach(prepareImageLink);
function prepareImageLink(link) {
var retries = 3; // current image number of retries
var image = new Image();
/** option: hide all links then reveal those with preloaded image
link.style.visibility = 'hidden';
image.onload = function() {
link.style.visibility = '';
};
**/
image.onerror = function() {
if(retries === 0) {
link.style.visibility = 'hidden'; // hide the link if image doesn't exist
//link.remove(); // option - remove the link if image doesn't exist
return;
}
retries--;
setTimeout(function() {
image.src = image.src + '?' + Date.now(); // for image to reload by adding the current dateTime to url
}, 1000); // the amount of time to wait before retry, change it to fit you're system
};
image.src = (link.href + ".png"); // add .substr(7) in your case
/** This is instead of 'javascript: showtouser("' + imgArray[i].src.substr(7) + '");'; which won't work on some browsers **/
link.addEventListener('mouseover', function() {
document.getElementById('image').src = image.src; // change to your showtouser func
});
}

Changing image extension with javascript/jquery only?

I'm making a simple slider to show off artwork for a friend of mine. I'm really only familiar with javascript/jquery, so I'm not 100% comfortable using something else right now.
Since my friend doesn't have any programming knowledge, I'm trying to keep this really simple for her to update (i.e., automating creating new images whenever she adds a new one to the folder). She will upload images to a folder and will have to number them (i.e., 1.jpg, 2.jpg). My javascript uses a for loop to loop through numbers (she will have to update the loop whenever she adds a new image) and insert them into the file name. HOWEVER this limits her to only uploading one type of file. Is there someway to change the extension only using javascript?
This is what I have so far:
function callImages(){
//create the image div
$('.artslider').append('<div class="image"></div>');
//create the files array
var files = [];
//start the loop, starting position will have to be updated as images are added
for (i=8;i>=0;i--){
//create the img src for a jpg img
var imgJPG = 'arts/'+i+'.jpg';
//find the natural width of the image after it loads to see if it actually exists
var imgWidth = $('imgJPG').load().naturalWidth;
//if the width is undefined, replace the jpg extension with gif
if (imgWidth===undefined){
var imgGIF = imgJPG.replace('jpg', 'gif');
files[i] = '<img src="'+imgGIF+'" class="artsliderimg"/>';
}
//otherwise keep the jpg extension
else {
files[i] = '<img src="'+imgJPG+'" class="artsliderimg"/>';
}
//then add the images to the img div
$('.image').append(files[i]);
}
};
The problem with this if/else is that it will only create a gif image. If you switch the order, it will only create a jpg image.
edit: here's what this code produces: https://googledrive.com/host/0B1lNgklCWTGwV1N5cWNlNUJqMzg/index.html
The problem is with this bit of code:
var imgJPG = 'arts/'+i+'.jpg';
var imgWidth = $('imgJPG').load().naturalWidth;
imgWidth will always be undefined.
Firstly you are passing in the string 'imgJPG' instead of the parameter imgJPG. Secondly I think you have misunderstood jQuery selectors, this is used for selecting HTML elements, inputting a file path into here will not achieve anything. Thirdly I think you have misunderstood the load function, this is used for loading data from the server into a HTML element.
I would suggest using a function like below to check if the image exists:
function urlExists(url) {
var http = jQuery.ajax({
type:"HEAD",
url: url,
async: false
});
return http.status == 200;
}
Then in your code:
if (!urlExists(imgJPG)){
var imgGIF = imgJPG.replace('jpg', 'gif');
files[i] = '<img src="'+imgGIF+'" class="artsliderimg"/>';
}
else {
files[i] = '<img src="'+imgJPG+'" class="artsliderimg"/>';
}

Image resizing client-side with JavaScript before upload to the server

I am looking for a way to resize an image client-side with JavaScript (really resize, not just change width and height).
I know it's possible to do it in Flash but I would like to avoid it if possible.
Is there any open source algorithm somewhere on the web?
Here's a gist which does this:
https://gist.github.com/dcollien/312bce1270a5f511bf4a
(an es6 version, and a .js version which can be included in a script tag)
You can use it as follows:
<input type="file" id="select">
<img id="preview">
<script>
document.getElementById('select').onchange = function(evt) {
ImageTools.resize(this.files[0], {
width: 320, // maximum width
height: 240 // maximum height
}, function(blob, didItResize) {
// didItResize will be true if it managed to resize it, otherwise false (and will return the original file as 'blob')
document.getElementById('preview').src = window.URL.createObjectURL(blob);
// you can also now upload this blob using an XHR.
});
};
</script>
It includes a bunch of support detection and polyfills to make sure it works on as many browsers as I could manage.
(it also ignores gif images - in case they're animated)
The answer to this is yes - in HTML 5 you can resize images client-side using the canvas element. You can also take the new data and send it to a server. See this tutorial:
http://hacks.mozilla.org/2011/01/how-to-develop-a-html5-image-uploader/
If you were resizing before uploading I just found out this http://www.plupload.com/
It does all the magic for you in any imaginable method.
Unfortunately HTML5 resize only is supported with Mozilla browser, but you can redirect other browsers to Flash and Silverlight.
I just tried it and it worked with my android!
I was using http://swfupload.org/ in flash, it does the job very well, but the resize size is very small. (cannot remember the limit) and does not go back to html4 when flash is not available.
http://nodeca.github.io/pica/demo/
In modern browser you can use canvas to load/save image data. But you should keep in mind several things if you resize image on the client:
You will have only 8bits per channel (jpeg can have better dynamic range, about 12 bits). If you don't upload professional photos, that should not be a problem.
Be careful about resize algorithm. The most of client side resizers use trivial math, and result is worse than it could be.
You may need to sharpen downscaled image.
If you wish do reuse metadata (exif and other) from original - don't forget to strip color profile info. Because it's applied when you load image to canvas.
Perhaps with the canvas tag (though it's not portable). There's a blog about how to rotate an image with canvas here, I suppose if you can rotate it, you can resize it. Maybe it can be a starting point.
See this library also.
You can use a javascript image processing framework for client-side image processing before uploading the image to the server.
Below I used MarvinJ to create a runnable code based on the example in the following page:
"Processing images in client-side before uploading it to a server"
Basically I use the method Marvin.scale(...) to resize the image. Then, I upload the image as a blob (using the method image.toBlob()). The server answers back providing a URL of the received image.
/***********************************************
* GLOBAL VARS
**********************************************/
var image = new MarvinImage();
/***********************************************
* FILE CHOOSER AND UPLOAD
**********************************************/
$('#fileUpload').change(function (event) {
form = new FormData();
form.append('name', event.target.files[0].name);
reader = new FileReader();
reader.readAsDataURL(event.target.files[0]);
reader.onload = function(){
image.load(reader.result, imageLoaded);
};
});
function resizeAndSendToServer(){
$("#divServerResponse").html("uploading...");
$.ajax({
method: 'POST',
url: 'https://www.marvinj.org/backoffice/imageUpload.php',
data: form,
enctype: 'multipart/form-data',
contentType: false,
processData: false,
success: function (resp) {
$("#divServerResponse").html("SERVER RESPONSE (NEW IMAGE):<br/><img src='"+resp+"' style='max-width:400px'></img>");
},
error: function (data) {
console.log("error:"+error);
console.log(data);
},
});
};
/***********************************************
* IMAGE MANIPULATION
**********************************************/
function imageLoaded(){
Marvin.scale(image.clone(), image, 120);
form.append("blob", image.toBlob());
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://www.marvinj.org/releases/marvinj-0.8.js"></script>
<form id="form" action='/backoffice/imageUpload.php' style='margin:auto;' method='post' enctype='multipart/form-data'>
<input type='file' id='fileUpload' class='upload' name='userfile'/>
</form><br/>
<button type="button" onclick="resizeAndSendToServer()">Resize and Send to Server</button><br/><br/>
<div id="divServerResponse">
</div>
In my experience, this example has been the best solution for uploading a resized picture: https://zocada.com/compress-resize-images-javascript-browser/
It uses the HTML5 Canvas feature.
The code is as 'simple' as this:
compress(e) {
const fileName = e.target.files[0].name;
const reader = new FileReader();
reader.readAsDataURL(e.target.files[0]);
reader.onload = event => {
const img = new Image();
img.src = event.target.result;
img.onload = () => {
const elem = document.createElement('canvas');
const width = Math.min(800, img.width);
const scaleFactor = width / img.width;
elem.width = width;
elem.height = img.height * scaleFactor;
const ctx = elem.getContext('2d');
// img.width and img.height will contain the original dimensions
ctx.drawImage(img, 0, 0, width, img.height * scaleFactor);
ctx.canvas.toBlob((blob) => {
const file = new File([blob], fileName, {
type: 'image/jpeg',
lastModified: Date.now()
});
}, 'image/jpeg', 1);
},
reader.onerror = error => console.log(error);
};
}
There are two downsides with this solution.
The first one is related with the image rotation, due to ignoring EXIF data. I couldn't tackle this issue, and wasn't so important in my use case, but will be glad to hear any feedback.
The second downside is the lack of support foe IE/Edge (not the Chrome based version though), and I won't put any time on that.
Yes, with modern browsers this is totally doable. Even doable to the point of uploading the file specifically as a binary file having done any number of canvas alterations.
http://jsfiddle.net/bo40drmv/
(this answer is a improvement of the accepted answer here)
Keeping in mind to catch process the result submission in the PHP with something akin to:
//File destination
$destination = "/folder/cropped_image.png";
//Get uploaded image file it's temporary name
$image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
//Move temporary file to final destination
move_uploaded_file($image_tmp_name, $destination);
If one worries about Vitaly's point, you can try some of the cropping and resizing on the working jfiddle.

Categories