HTML5 Canvas toDataURL is always the same in a for loop - javascript

Consider this JSFiddle. In it I select photos which I then want to base64 encode using canvas so I can store them in sessionStorage for deferred uploading. Because I have it set for multiple files, I loop through each one and create an image and a canvas, but no matter what it just seems to output the exact same base64 encoded image every time. Through testing I know that on each loop iteration the image is different and does indeed point to a different file blob, but the canvas is just outputting the same thing over and over, which I think is also the last file in the files list. Sometimes it will also just output a "data," string and that's it. I'd love it if someone can point me in the right direction.
Code is show below:
HTML
<style type="text/css">
input[type="file"] {
display: none;
}
a {
display: inline-block;
margin: 6px;
}
</style>
<form action="#" method="post">
<input type="file" accept="image/*" multiple />
<button type="button">Select Photos</button>
</form>
<nav></nav>
JavaScript
console.clear();
$(function () {
$("button[type=button]").on("click", function (e) {
$("input[type=file]").trigger("click");
});
$("input[type=file]").on("change", function (e) {
var nav = $("nav").empty();
for (var i = 0, l = this.files.length; i < l; i++) {
var file = this.files[i],
image = new Image();
$(image).on("load", i, function (e) {
var canvas = document.createElement("canvas"),
context = canvas.getContext("2d");
canvas.height = this.height;
canvas.width = this.width;
context.drawImage(this, 0, 0);
nav.append("" + e.data + "");
URL.revokeObjectURL(this.src);
});
image.src = URL.createObjectURL(file);
}
});
});

but no matter what it just seems to output the exact same base64
encoded image every time.
.load() event is asynchronous. You can use $.when() , $.Deferred(), substitute $.map() for for loop to handle asynchronously loaded img elements. The caveat that the displayed a element text may not be in numerical order; which can be adjusted by sorting the elements at .then(); though not addressed at Question, if required, the listing or loading of images sequentially can also be achieved.
$("input[type=file]").on("change", function(e) {
var nav = $("nav").empty();
var file = this.files
$.when.apply($, $.map(file, function(img, i) {
return new $.Deferred(function(dfd) {
var image = new Image();
$(image).on("load", i, function(e) {
var canvas = document.createElement("canvas")
, context = canvas.getContext("2d");
canvas.height = this.height;
canvas.width = this.width;
context.drawImage(this, 0, 0);
nav.append("<a href=\""
+ canvas.toDataURL()
+ "\" target=\"_blank\">"
+ e.data + "</a>");
dfd.resolve()
URL.revokeObjectURL(this.src);
});
image.src = URL.createObjectURL(img);
return dfd.promise()
})
})).then(function() {
nav.find("a").each(function() {
console.log(this.href + "\n");
})
})
});
})
jsfiddle https://jsfiddle.net/bc6x3s02/19/

Related

How to retain order of array using each and image.onload

I'm trying to use image.onload function but async issue is occuring. I'm passing the data through array but it is not executing according to array order in the image.onload function. How can I fix this issue?
Object.entries(data).forEach(element => {
const brushtype = element[0]['brush']
const img = new Image();
img.onload = () => {
brushctx.drawImage(img, 0, 0);
}
img.src = brushtype;
}
The problem is the way you're requesting those images.
By doing
Object.entries(data).forEach(element => {
}
it's happening at the same time virtually. Whatever image finished loading will be drawn right-after.
If you want your images to be drawn in a specific order - e.g. the order it's listed in the array - you need to request a new one after the previous finished loading.
Here's an example:
let canvas = document.getElementById('canvas');
let context = canvas.getContext('2d');
let myImages = ['https://picsum.photos/id/1/50/50', 'https://picsum.photos/id/22/50/50', 'https://picsum.photos/id/83/50/50', 'https://picsum.photos/id/64/50/50'];
function loadImages(index) {
let image = new Image();
image.onload = function(e) {
context.drawImage(e.target, index % 2 * 50, parseInt(index / 2) * 50);
if (index + 1 < myImages.length) {
loadImages(index + 1);
}
}
image.src = myImages[index];
}
loadImages(0);
<canvas id='canvas' width='200' height='200'></canvas>
To draw images in order of request you either need to wait until all images have loaded and then draw them only when the earlier image have been draw. This answer gives an example of both approaches.
Note That if there are any load errors none of the images may be drawn. In the second method an error will mean none to all but one are drawn.
Load first then draw
The snippet bellow loads first then draws. It stores the images in an array images and keeps a count of images loading. Counting up for each new image, and when an image load it counts down.
When the counter is zero when know all images have loaded and can then draw them all
var imgCount = 0;
const images = [];
Object.entries(data).forEach(element => {
const img = new Image;
img.src = element[0]['brush'];
images.push(img);
imgCount ++;
img.onload = () => {
imgCount --;
if (!imgCount) {
for(const img of images) { brushctx.drawImage(img, 0, 0) }
}
}
}
Draw only when first or previous loaded.
The alternative is to again create an array to hold all the images. But this time we track what has been drawn. Each time an image is loaded, we check if any of the images in the array before it have loaded, then we draw all images until we find a empty image slot.
var drawnCount = 0;
const images = [];
Object.entries(data).forEach(element => {
const img = new Image;
const imgIdx = images.length;
img.src = element[0]['brush'];
img.onload = () => {
images[imgIdx] = img;
while (images[drawnCount]) { brushctx.drawImage(images[drawnCount++], 0, 0) }
}
images.push(null);
}

JavaScript: How to load images sequentially

Sorry about my English, this is not my native language.
I'm trying to compress multiple images from a input file. But when I tried to put the
img.onload = function() {}
inside the for loop that contains the sequence of files, the results of the append are totally out of order.
I need the images in sequence because I am mounting a PDF with the images, bus number, and photo date.
The original images are too big, 3MB+. And the resultant .pdf file is 62MB. It's unavailable.
var filesInput = $("#file"); filesInput.on("change", function(e) {
$("#divImagesFrame").show();
/* Manipulação das fotos */
var files = e.target.files;
var result = $("#divImagesFrame");
var filesLength = this.files.length;
var page = 1;
var count = 6;
$("#blur, #pageName").html($("#txtCliente").val());
result.append("<div class='pageTitle'><div class='pageBox'>" + page + "</div><span>" + $("#txtCliente").val() + "<span class='localName'>" + $("#txtCampanha").val() + "</span></span></div>");
for (let i = 0; i < this.files.length; i++) {
qtdFotos++;
let url = URL.createObjectURL(this.files[i]);
let img = new Image();
img.src = url;
console.log("ID outside img.load: " + i);
img.onload = function(){
console.log("ID inside img.load: " + i);
var busNumber = files[i].name.toString().split(".")[0];
var dataFoto = files[i].lastModified;
var d = new Date(dataFoto);
d.toLocaleString();
var canvas = document.createElement('canvas');
canvas.width = 460;
canvas.height = 360;
ctx = canvas.getContext('2d');
ctx.drawImage(img, 0,0, canvas.width, canvas.height);
var imageFile = canvas.toDataURL("image/JPEG", 1.0);
/* In this result.append, if I put the image source = img.src the order of images stay ok, but the size is much larger */
result.append("<div class='imageFrame' style='width:460px; border-radius:10px; height:360px; float:left; margin:14.01px 20px; box-shadow:1px 1px 3px 0.8px #999;'>" +
"<div style='float:left;border-radius:10px 10px 0 0; background:linear-gradient(-90deg,#105e86,#125e86); display:block; width:450px; height:40px; font-weight:550; font-size:23px; line-height:40px; padding-left:10px; color:#FFF;'>" + busNumber + "<span style='font-size:18px; float:right; margin-right:20px;'>" + d.toLocaleDateString() + "</span></div>" +
"<img width='460px' height='320px' style='border-radius:0 0 10px 10px; margin-bottom:5px;' src='" + imageFile + "'/></div>");
}
}
sammiCreateBook();
});
Here is how it has to be:
And here is the result of img.load, totally out of order.
I hope can you help me, thank you very much.
Problem solved
Hi guys. Thank you. The problem has been solved using javascript promises to verify if each function has been completed. Thanks again!
Store the image's URLs in an array
Define a function which executes your loading behaviour (the block inside your for loop)
At the beginning of your function you need to take the first URL from that array and remove it
At the end of your function body you need to invoke the function again.
Invoke the function.
Here's an example:
var images = [
'1.jpg',
'2.jpg',
'3.jpg',
];
var loadImage = function() {
if (!images.length) {
// Will be executed when all images are loaded
return;
}
var image = images.shift();
console.log('loading image ' + image);
// Your for-loop logic here
...
img.onload = function() {
console.log('image loaded ' + image);
// Your onload logic here ...
// -> important!
loadImage();
};
}
loadImage();

Using previous and next buttons to cycle through images in javascript

I'm writing a small mobile application using Javascript.
I'm making calls to a remote API that responds with some JSON formatted data. Within that data are URLs for images of products i need to display in my app. I've parsed the JSON into a JS array and now have an array of all the URLs for the images. When i first load my app i have a canvas which is used to display the first image from the array. I have two buttons, a previous and a next. I'd like to be able to cycle through the images using these buttons by using the URLs in the array to draw onto the canvas.
Any ideas?
Is something like the following what you're looking for?
// Image URLs
var imageUrls = ['http://emojipedia-us.s3.amazonaws.com/cache/8b/bd/8bbd3405b0a197214e229428c23dbe60.png', 'http://emojipedia-us.s3.amazonaws.com/cache/05/d1/05d1ba284ee1a3bfe4e0f68988baafb9.png', 'http://emojipedia-us.s3.amazonaws.com/cache/99/4c/994c5997c7a509703cc53ec2000bb258.png', 'http://emojipedia-us.s3.amazonaws.com/cache/59/0c/590c832e73a472c416bf9d8bfdd02a4a.png'];
// Keep track of the index of the image URL in the array above
var imageShownIndex = 0;
// Create a canvas
var canvas = document.createElement('canvas');
var canvasContext = canvas.getContext('2d');
canvas.height = 150;
canvas.width = 150;
// Create a button that will load the previous image on the canvas when clicked
var previousButton = document.createElement('button');
previousButton.innerHTML = 'Previous Image';
previousButton.onclick = function () {
// Show images in a cycle, so when you get to the beginning of the array, you loop back to the end
imageShownIndex = (imageShownIndex==0) ? imageUrls.length-1 : imageShownIndex-1;
updateImage();
};
// Do same for the next button
var nextButton = document.createElement('button');
nextButton.innerHTML = 'Next Image';
nextButton.onclick = function () {
imageShownIndex = (imageShownIndex == imageUrls.length-1) ? 0 : imageShownIndex+1;
updateImage();
};
document.body.appendChild(previousButton);
document.body.appendChild(canvas);
document.body.appendChild(nextButton);
// Show the first image without requiring a click
updateImage();
function updateImage() {
// Create the Image object, using the URL from the array as the source
// You could pre-load all the images and store them in the array, rather than loading each image de novo on a click
var img = new Image();
img.src = imageUrls[imageShownIndex];
// Clear the canvas
canvasContext.clearRect(0, 0, 150, 150);
// After the image has loaded, draw the image on the canvas
img.onload = function() {
canvasContext.drawImage(img, 0, 0);
}
}
EDIT: Or if you have HTML elements already made, you can just use JavaScript to control the canvas. E.g.:
<html>
<body>
<button id="previous_button" onclick="goToPreviousImage()">Previous Image</button>
<canvas id="image_canvas" height=150, width=150></canvas>
<button id="next_button" onclick="goToNextImage()">Next Image</button>
</body>
<script>
var imageUrls = ['http://emojipedia-us.s3.amazonaws.com/cache/8b/bd/8bbd3405b0a197214e229428c23dbe60.png', 'http://emojipedia-us.s3.amazonaws.com/cache/05/d1/05d1ba284ee1a3bfe4e0f68988baafb9.png', 'http://emojipedia-us.s3.amazonaws.com/cache/99/4c/994c5997c7a509703cc53ec2000bb258.png', 'http://emojipedia-us.s3.amazonaws.com/cache/59/0c/590c832e73a472c416bf9d8bfdd02a4a.png'];
var imageShownIndex = 0;
var canvas = document.getElementById('image_canvas');
var canvasContext = canvas.getContext('2d');
function goToPreviousImage() {
imageShownIndex = (imageShownIndex==0) ? imageUrls.length-1 : imageShownIndex-1;
updateImage();
};
function goToNextImage() {
imageShownIndex = (imageShownIndex == imageUrls.length-1) ? 0 : imageShownIndex+1;
updateImage();
};
updateImage();
function updateImage() {
var img = new Image();
img.src = imageUrls[imageShownIndex];
canvasContext.clearRect(0, 0, 150, 150);
img.onload = function() {
canvasContext.drawImage(img, 0, 0);
}
}
</script>
</html>

dynamic image variable with onclick event

I have several canvases. I also have several picture URLs. I want to draw all pictures on the canvas. There is a problem in the drawing function. Drawing the image only works when the image loads completely, but I have to draw the image as it loads. I wrote following code:
for (var i = 2; i < length; i++) {
canvid[i] = "canv" + i;
img[i] = new Image();
img[i].src = "..\\images\\UploadImage\\"+ name + i + ".jpg";
img[i].onload = function () {
var c = document.getElementById(canvId[i]);
var cDraw = c.getContext("2d");
cDraw.drawImage(img[i], 0, 0);
};
I know this code has error, it's kind of pseudo code to show what I want.
Put your logic in
$(documet).ready(function(){
//logic
});
the answer is in following link
stack overflow link
when you want to call on click event on image variable you have to wait for it
so you couldn't use loop you have to put next call on previous image on load event .
var loadImages = function (imageURLarray) {
if (!(startPage < pages))
return;
canvid = "canv" + i;
img.src = imageURLarray[startPage];
// your top code
img.onload = function (e) {
// code, run after image load
var c = document.getElementById(canvid);
var cDraw = c.getContext("2d");
cDraw.drawImage(img, 0, 0);
startPage++;
loadImages(imageURLarray);
}
}
loadImages(imageURLarray);

<img>width returns 0 under heavy load

The below handleFiles method is being passed files from both drag and drop and a file input. After it gets the data url for a given file it passes it to the processImage function. This function creates a new image and sets the src and file for that image. I then take different actions based on the width of the incoming image and insert the image into the dom. However, I noticed when dropping in a bunch of images imageWidth will get set to 0. I have confirmed the image.src is correctly set and that dropping the same image in by itself works fine. I also have confirmed that if I remove the width calculations the image does display correctly on the page. When I enter the debugger I can confirm that immediately after imageWidth is set to 0 i.width returns a correct value. At first I thought it might be a threading issue in Chrome, but then when I saw it happen in FireFox as well I became alarmed. I have not been able to pinpoint a certain threshold, but the more load you put on the browser the less likely it is to correctly get the width.
I am seeing the problem in both FireFox 16.0.2 and Chrome 23.0.1271.95.
function handleFiles(files) {
for (var i = 0; i < files.length; i++) {
var file = files[i];
if( !isImage(file) ) {
continue;
}
var reader = new FileReader();
reader.onloadend = function(e) {
var dataURL = e.target.result;
processImage(file, dataURL);
}
reader.readAsDataURL(file);
}
}
function processImage(file, dataURL) {
var i = new Image();
i.src = dataURL;
i.file = file;
//console.log(i);
var maxWidth = 600;
var imageWidth = i.width;
......
}
As with all images, they may need time to load before they will tell you their width:
var i = new Image();
i.onload = function() {
//console.log(i);
var maxWidth = 600;
var imageWidth = this.width;
}
i.src = dataURL;
i.file = file;
The width (and height) might be 0 because it's not loaded yet.
Try adding the load event like so:
function processImage(file, dataURL) {
var i = new Image();
i.addEventListener("load", function () {
var maxWidth = 600;
var imageWidth = i.width;
......
});
i.src = dataURL;
i.file = file;
}

Categories

Resources