JavaScript: How to load images sequentially - javascript

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

Related

How to detect, that drawing a canvas object is finished?

I have following JS code (found here, on stackoverflow, and a little-bit modded), which resize image on client side using canvas.
function FileListItem(a) {
// Necesary to proper-work of CatchFile function (especially image-resizing).
// Code by Jimmy Wärting (https://github.com/jimmywarting)
a = [].slice.call(Array.isArray(a) ? a : arguments)
for (var c, b = c = a.length, d = !0; b-- && d;) d = a[b] instanceof File
if (!d) throw new TypeError('expected argument to FileList is File or array of File objects')
for (b = (new ClipboardEvent('')).clipboardData || new DataTransfer; c--;) b.items.add(a[c])
return b.files
}
function CatchFile(obj) {
// Based on ResizeImage function.
// Original code by Jimmy Wärting (https://github.com/jimmywarting)
var file = obj.files[0];
// Check that file is image (regex)
var imageReg = /[\/.](gif|jpg|jpeg|tiff|png|bmp)$/i;
if (!file) return
var uploadButtonsDiv = document.getElementById('upload_buttons_area');
// Check, that it is first uploaded file, or not
// If first, draw a div for showing status
var uploadStatusDiv = document.getElementById('upload_status_area');
if (!uploadStatusDiv) {
var uploadStatusDiv = document.createElement('div');
uploadStatusDiv.setAttribute('class', 'upload-status-area');
uploadStatusDiv.setAttribute('id', 'upload_status_area');
uploadButtonsDiv.parentNode.insertBefore(uploadStatusDiv, uploadButtonsDiv.nextSibling);
// Draw sub-div for each input field
var i;
for (i = 0; i < 3; i++) {
var uploadStatus = document.createElement('div');
uploadStatus.setAttribute('class', 'upload-status');
uploadStatus.setAttribute('id', ('upload_status_id_commentfile_set-' + i + '-file'));
uploadStatusDiv.append(uploadStatus);
}
}
var canvasDiv = document.getElementById('canvas-area');
var currField = document.getElementById(obj.id);
var currFieldLabel = document.getElementById(('label_' + obj.id));
// Main image-converting procedure
if (imageReg.test(file.name)) {
file.image().then(img => {
const canvas = document.createElement('canvas')
canvas.setAttribute('id', ('canvas_' + obj.id));
const ctx = canvas.getContext('2d')
const maxWidth = 1600
const maxHeight = 1200
// Calculate new size
const ratio = Math.min(maxWidth / img.width, maxHeight / img.height)
const width = img.width * ratio + .5|0
const height = img.height * ratio + .5|0
// Resize the canvas to the new dimensions
canvas.width = width
canvas.height = height
// Drawing canvas-object is necessary to proper-work
// on mobile browsers.
// In this case, canvas is inserted to hidden div (display: none)
ctx.drawImage(img, 0, 0, width, height)
canvasDiv.appendChild(canvas)
// Get the binary (aka blob)
canvas.toBlob(blob => {
const resizedFile = new File([blob], file.name, file)
const fileList = new FileListItem(resizedFile)
// Temporary remove event listener since
// assigning a new filelist to the input
// will trigger a new change event...
obj.onchange = null
obj.files = fileList
obj.onchange = CatchFile
}, 'image/jpeg', 0.70)
}
)
// If file is image, during conversion show status
function ShowConvertConfirmation() {
if (document.getElementById('canvas_' + obj.id)) {
document.getElementById(('upload_status_' + obj.id)).innerHTML =
'<font color="#4CAF50">Konwertowanie pliku ' + file.name + ' zakończone!</font>';
return true;
}
else {
document.getElementById(('upload_status_' + obj.id)).innerHTML =
'<font color="#4CAF50">Konwertowanie pliku ' + file.name + ' zakończone!</font>';
return false;
}
}
// Loop ShowConvertConfirmation function untill return true (file is converted)
var convertConfirmationLoop = setInterval(function() {
var isConfirmed = ShowConvertConfirmation();
if (!isConfirmed) {
ShowConvertConfirmation();
}
else {
// Break loop
clearInterval(convertConfirmationLoop);
}
}, 2000); // Check every 2000ms
}
// If file is not an image, show status with filename
else {
document.getElementById(('upload_status_' + obj.id)).innerHTML =
'<font color="#4CAF50">Dodano plik ' + file.name + '</font>';
//uploadStatusDiv.append(uploadStatus);
}
}
Canvas is drawn in hidden div:
<div id="canvas-area" style="overflow: hidden; height: 0;"></div>
I am only detect, that div canvas-area is presented and basing on this, JS append another div with status.
Unfortunatelly on some mobile devices (mid-range smartphones), message will be showed before finish of drawing (it is wrong). Due to this, some uploaded images are corrupted or stay in original size.
How to prevent this?
Everything that should happen after the image has loaded, should be executed within the then callback, or called from within it.
It is important to realise that the code that is not within that callback will execute immediately, well before the drawing has completed.

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

HTML5 Canvas toDataURL is always the same in a for loop

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/

jQuery/javascript - Get Image size before load

I have a list of images that is rendered as thumbnails after upload. The issue that I have is I need the dimensions of the fully uploaded images, so that I can run a resize function if sized incorrectly.
Function that builds the image list
function buildEditList(json, galleryID)
{
//Hide the list
$sortable = $('#sortable');
$sortable.hide();
for(i = 0, j = json.revision_images.length; i < j; i++) {
$sortable.append(
"<li id='image_" + json.revision_images[i].id + "'><a class=\"ui-lightbox\" href=\"../data/gallery/"
+ galleryID
+ "/images/album/"
+ json.revision_images[i].OrgImageName
+ "\"><img id=\""
+ json.revision_images[i].id
+ "\" alt=\"\" src=\"../data/gallery/"
+ galleryID
+ "/images/album/"
+ json.revision_images[i].OrgImageName
+ "\"/></a></li>"
).hide();
}
//Set first and last li to 50% so that it fits the format
$('#sortable li img').first().css('width','50%');
$('#sortable li img').last().css('width', '50%');
//Fade images back in
$sortable.fadeIn("fast",function(){
var img = $("#703504"); // Get my img elem -- hard coded at the moment
var pic_real_width, pic_real_height;
$("<img/>") // Make in memory copy of image to avoid css issues
.attr("src", $(img).attr("src"))
.load(function() {
pic_real_width = this.width; // Note: $(this).width() will not
pic_real_height = this.height; // work for in memory images.
});
alert(pic_real_height);
});
}
I have been trying to use the solution from this stackoverflow post, but have yet to get it to work, or it may just be my code. if you have any ideas on this I could use the help. Currently the alert is coming back undefined.
There's a newer js method called naturalHeight or naturalWidth that will return the information you're looking for. Unfortunately it wont work in older IE versions. Here's a function I created a few months back that may work for you. I added a bit of your code below it for an example:
function getNatural($mainImage) {
var mainImage = $mainImage[0],
d = {};
if (mainImage.naturalWidth === undefined) {
var i = new Image();
i.src = mainImage.src;
d.oWidth = i.width;
d.oHeight = i.height;
} else {
d.oWidth = mainImage.naturalWidth;
d.oHeight = mainImage.naturalHeight;
}
return d;
}
var img = $("#703504");
var naturalDimension = getNatural(img);
alert(naturalDimension.oWidth, naturalDimension.oHeight)​

Resizing dynamic images not working more than one time

I've got a little problem here. I've been trying to do an image gallery with JavaScript but there's something that I got a problem with. I can get the image to resize when the first image load, but as soon as I load another image, it won't resize anymore! Since the user will be able to upload a lot of different size pictures, I really need to make it work.
I've checked for ready-to-use image gallery and such and nothing was doing what I need to do.
Here's my javascript:
function changeCurrentImage(conteneur)
{
var img = conteneur.getElementsByTagName("img");
var imgUrl = img[0].src;
var imgFirstPart = imgUrl.substring(0, imgUrl.lastIndexOf('.') - 9);
var imgLastPart = imgUrl.substring(imgUrl.lastIndexOf('.'));
var currentImg = document.getElementById('currentImage');
currentImg.src = imgFirstPart + "borne" + imgLastPart;
resize(document.getElementById('currentImage'), 375, 655);
}
function resize(img, maxh, maxw) {
var ratio = maxh/maxw;
if (img.height/img.width > ratio){
// height is the problem
if (img.height > maxh){
img.width = Math.round(img.width*(maxh/img.height));
img.height = maxh;
}
} else {
// width is the problem
if (img.width > maxw){
img.height = Math.round(img.height*(maxw/img.width));
img.width = maxw;
}
}
};
Here's the HTML (using ASP.Net Repeater):
<asp:Repeater ID="rptImages" runat="server">
<HeaderTemplate>
</HeaderTemplate>
<ItemTemplate>
<a href="#">
<div id="thumbnailImageContainer1" onclick="changeCurrentImage(this)">
<div id="thumbnailImageContainer2">
<img id="thumbnailImage" src="<%# SiteUrl + Eval("ImageThumbnailPath")%>?rn=<%=Random()%>" alt="Photo" onload="resize(this, 60, 105)" />
</div>
</div>
</a>
</ItemTemplate>
<FooterTemplate>
</FooterTemplate>
</asp:Repeater>
Most likely the image is not yet downloaded so img.height and img.width are not yet there. Technically you don't need to wait till the whole image is downloaded, you can poll the image in a timer until width and height are non-zero. This sounds messy but can be done nicely if you take the time to do it right. (I have an ImageLoader utility I made for this purpose....has only one timer even if it is handling multiple images at once, and calls a callback function when it has the sizes) I have to disagree with Marcel....client side works great for this sort of thing, and can work even if the images are from a source other than your server.
Edit: add ImageLoader utility:
var ImageLoader = {
maxChecks: 1000,
list: [],
intervalHandle : null,
loadImage : function (callback, url, userdata) {
var img = new Image ();
img.src = url;
if (img.width && img.height) {
callback (img.width, img.height, url, 0, userdata);
}
else {
var obj = {image: img, url: url, callback: callback,
checks: 1, userdata: userdata};
var i;
for (i=0; i < this.list.length; i++) {
if (this.list[i] == null)
break;
}
this.list[i] = obj;
if (!this.intervalHandle)
this.intervalHandle = setInterval(this.interval, 30);
}
},
// called by setInterval
interval : function () {
var count = 0;
var list = ImageLoader.list, item;
for (var i=0; i<list.length; i++) {
item = list[i];
if (item != null) {
if (item.image.width && item.image.height) {
item.callback (item.image.width, item.image.height,
item.url, item.checks, item.userdata);
ImageLoader.list[i] = null;
}
else if (item.checks > ImageLoader.maxChecks) {
item.callback (0, 0, item.url, item.checks, item.userdata);
ImageLoader.list[i] = null;
}
else {
count++;
item.checks++;
}
}
}
if (count == 0) {
ImageLoader.list = [];
clearInterval (ImageLoader.intervalHandle);
delete ImageLoader.intervalHandle;
}
}
};
Example usage:
var callback = function (width, height, url, checks, userdata) {
// show stuff in the title
document.title = "w: " + width + ", h:" + height +
", url:" + url + ", checks:" + checks + ", userdata: " + userdata;
var img = document.createElement("IMG");
img.src = url;
// size it to be 100 px wide, and the correct
// height for its aspect ratio
img.style.width = "100px";
img.style.height = ((height/width)*100) + "px";
document.body.appendChild (img);
};
ImageLoader.loadImage (callback,
"http://upload.wikimedia.org/wikipedia/commons/thumb/" +
"1/19/Caerulea3_crop.jpg/800px-Caerulea3_crop.jpg", 1);
ImageLoader.loadImage (callback,
"http://upload.wikimedia.org/wikipedia/commons/thumb/" +
"8/85/Calliphora_sp_Portrait.jpg/402px-Calliphora_sp_Portrait.jpg", 2);
With the way you have your code setup, I would try and call your resize function from an onload event.
function resize() {
var img = document.getElementById('currentImage');
var maxh = 375;
var maxw = 655;
var ratio = maxh/maxw;
if (img.height/img.width > ratio){
// height is the problem
if (img.height > maxh){
img.width = Math.round(img.width*(maxh/img.height));
img.height = maxh;
}
} else {
// width is the problem
if (img.width > maxw){
img.height = Math.round(img.height*(maxw/img.width));
img.width = maxw;
}
}
};
function changeCurrentImage(conteneur)
{
var img = conteneur.getElementsByTagName("img");
img.onload = resize;
var imgUrl = img[0].src;
var imgFirstPart = imgUrl.substring(0, imgUrl.lastIndexOf('.') - 9);
var imgLastPart = imgUrl.substring(imgUrl.lastIndexOf('.'));
var currentImg = document.getElementById('currentImage');
currentImg.src = imgFirstPart + "borne" + imgLastPart;
}
I would play around with that. Maybe use global variables for your maxH/W and image ID(s);
#Comments: No, I can't do that server side since it would refresh the page everytime someone click on a new image. That would be way too bothersome and annoying for the users.
As for the thumbnails, those image are already saved in the appropriate size. Only the big image that shows is about 33% of its size. Since we already have 3 images PER uploaded images, I didn't want to upload a 4th one for each upload, that would take too much server space!
As for the "currentImage", I forgot to add it, so that might be helful lol:
<div id="currentImageContainer">
<div id="currentImageContainer1">
<div id="currentImageContainer2">
<img id="currentImage" src="#" alt="" onload="resize(this, 375, 655)" />
</div>
</div>
</div>
#rob: I'll try the ImageLoader class, that might do the trick.
I found an alternative that is working really well. Instead of changing that IMG width and height, I delete it and create a new one:
function changeCurrentImage(conteneur)
{
var thumbnailImg = conteneur.getElementsByTagName("img");
var thumbnailImgUrl = thumbnailImg[0].src;
var newImgUrl = thumbnailImgUrl.replace("thumbnail", "borne");
var currentImgDiv = document.getElementById('currentImageContainer2');
var currentImg = currentImgDiv.getElementById("currentImage");
if (currentImg != null)
{
currentImgDiv.removeChild(currentImg);
}
var newImg = document.createElement("img");
newImageDiv = document.getElementById('currentImageContainer2');
newImg.id = "currentImage";
newImg.onload = function() {
Resize(newImg, 375, 655);
newImageDiv.appendChild(newImg);
}
newImg.src = newImgUrl;
}
Also, in case people wonder, you MUST put the .onload before the .src when assigning an a new source for an image!

Categories

Resources