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

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

Related

Blob images does not show up at UI, although I can see them on console

I am trying to create multiple blob images, and show them for a video timeline UI. To create the images, I take screenshots of the video for some certain time frames, and create img elements, then add img.src as blob images' url. The problem is blob images does not show up at UI, although I can see them on console. Any ideas?
Code snippet from creation of blob images:
// draw the video frame to canvas
const ctx = canvas.getContext("2d");
ctx.drawImage(videoPlayer, 0, 0, canvas.width, canvas.height);
ctx.canvas.toBlob(
blob => {
resolve(blob);
},
"image/jpeg",
0.75 /* quality */
);
Code Snippet from creation of images:
let img = '';
for (let i = 0; i < count / 3; i++) {
const cover = await GetVideoCover(item.video_url, i);
var url = URL.createObjectURL(cover);
var image = new Image();
image.src = url;
img += "<img src='"+image.src+"'>";
console.log(img)
}
return img;
The console log from console.log(img)line:
actual html img elements:
When I manually update the source as below, it works, but I did not understand why it does not get the source by itself. Any idea would be appreciated.
Try registering an onload event listener on the image element and then handle all code that depends on that image in the event listener, like so:
for (let i = 0; i < count / 3; i++) {
const cover = await GetVideoCover(item.video_url, i);
var url = URL.createObjectURL(cover);
var image = new Image();
image.src = url;
image.addEventListener('load', (event) => {
// perform some magic here e.g append to DOM or draw the image to canvas
}
}
Thank you for the contributions, the answers did not work but they helped me in the process. I solved the problem today and wanted to share it here.
I should have mentioned before as the created images are fed to vis.js.
When I experimented more, I saw vis.js does not append the created objects, but it changes the innerHTML of the timeline. (vis.js might not support blob images as src of img tag)
Then, I decided to give a shot to append element. To do that, I created a new div element, and appended the created images to that div.
I also added an additional promise level to the blob image creation process, since some files were missing.
Code snippet from newly added promise level:
const blobToImage = (itemVideoURL,captureSecond) => {
return new Promise(async (resolve,reject)=> {
let cover = await GetVideoCover(itemVideoURL,captureSecond);
const url = URL.createObjectURL(cover)
let img = new Image()
img.onload = () => {
URL.revokeObjectURL(url)
resolve(img)
}
img.src = url
img.height = '75'
})
}
Code snippet from new function:
let divElement = document.createElement('div');
for (let i = 0; i < count * 3; i++) {
let newImage = await blobToImage(item.video_url,i+1)
divElement.appendChild(newImage)
}
return divElement;
It magically worked.
You should use a single image rather than multiple images.
Just concatanate all images together :
let imgEle1 = document.querySelectorAll(".image")[0];
let imgEle2 = document.querySelectorAll(".image")[1];
let resEle = document.querySelector(".result");
var context = resEle.getContext("2d");
let BtnEle = document.querySelector(".Btn");
BtnEle.addEventListener("click", () => {
resEle.width = imgEle1.width;
resEle.height = imgEle1.height;
context.globalAlpha = 1.0;
context.drawImage(imgEle1, 0, 0);
context.globalAlpha = 0.5;
context.drawImage(imgEle2, 0, 0);
});

How to add a 16x16 grid of pseudo-random tiles as a background in canvas (or other web language)

I have recently been working on a web based project using canvas on HTML5. The program consists of a 16x16 grid of tiles that have been pseudo-randomly generated. I am relatively new to canvas, but have built this program in several other environments, none of which however compile successfully to a web based language. this is the main code section that is giving me bother:
var A = 8765432352450986;
var B = 8765432352450986;
var M = 2576436549074795;
var X = 1;
var rx = 0;
var ry = 0;
this.image = new Image();
var i = 0;
var ii = 0;
while(i < 16)
{
while(ii < 16)
{
this.image = new Image();
this.image.src = "textures/grass.png";
x = (((A*X)+B)%M)%M;
if((x/2)%1 == 0)
{
this.image.src = "textures/grass.png";
}
if((x/8)%1 == 0)
{
this.image.src = "textures/hill.png";
}
if((x/21)%1 == 0)
{
this.image.src = "textures/trees.png";
}
if((x/24)%1 == 0)
{
this.image.src = "textures/sea.png";
}
if((x/55)%1 == 0)
{
this.image.src = "textures/mountain.png";
}
if((x/78)%1 == 0)
{
this.image.src = "textures/lake.png";
}
if((x/521)%1 == 0)
{
this.image.src = "textures/volcano.png";
}
if((x/1700)%1 == 0)
{
this.image.src = "textures/shrine.png";
}
if((x/1890)%1 == 0)
{
this.image.src = "textures/outpost.png";
}
if((x/1999)%1 == 0)
{
this.image.src = "textures/civ.png";
}
ctx = myGameArea.context;
ctx.drawImage(this.image,rx, ry, 20, 20);
ii ++;
rx += 20;
}
i ++;
rx = 0;
ry += 16;
}
I would like canvas to draw along the lines of this code above, effectively generating a grid like this
pre generated grid image
(please try and ignore the obvious bad tile drawings, I planned on either finding an artist or trying slightly harder on them when I get the game fully working.)
The black square is a separate movable object. I haven't got as far as implementing it in this version, but if you have any suggestions for it please tell me
in the full html file I have now, the canvas renders but none of the background (using the w3schools tutorials, I can make objects render however)
In short: how do I render a background consisting of a 16x16 grid of pseudo-random tiles on an event triggered or on page loaded, using canvas or if that does not work another web based technology
Thank you for your time.
A few problems but the main one is that you need to give an image some time to load before you can draw it to the canvas.
var image = new Image();
image.src = "image.png";
// at this line the image may or may not have loaded.
// If not loaded you can not draw it
To ensure an image has loaded you can add a onload event handler to the image
var image = new Image();
image.src = "image.png";
image.onload = function(){ ctx.drawImage(image,0,0); }
The onload function will be called after all the current code has run.
To load many images you want to know when all have loaded. One way to do this is to count the number of images you are loading, and then use the onload to count the number of images that have loaded. When the loaded count is the same as the loading count you know all have loaded and can then call a function to draw what you want with the images.
// Array of image names
const imageNames = "grass,hill,trees,sea,mountain,lake,volcano,shrine,outpost,civ".split(",");
const images = []; // array of images
const namedImages = {}; // object with named images
// counts of loaded and waiting toload images
var loadedCount = 0;
var imageCount = 0;
// tile sizes
const tileWidth = 20;
const tileHeight = 20;
// NOT SURE WHERE YOU GOT THIS FROM so have left it as you had in your code
// Would normally be from a canvas element via canvasElement.getContext("2d")
var ctx = myGameArea.context;
// seeded random function encapsulated in a singleton
// You can set the seed by passing it as an argument rand(seed) or
// just get the next random by not passing the argument. rand()
const rand = (function(){
const A = 8765432352450986;
const B = 8765432352450986; // This value should not be the same as A?? left as is so you get the same values
const M = 2576436549074795;
var seed = 1;
return (x = seed) => seed = ((A * x) + B) % M;
}());
// function loads an image with name
function addImage(name){
const image = new Image;
image.src = "textures/" + name + ".png";
image.onload = () => {
loadedCount += 1;
if(loadedCount === imageCount){
if(typeof allImagesLoaded === "function"){
allImagesLoaded();
}
}
}
imageCount += 1;
images.push(image);
namedImages[name] = image;
}
imageNames.forEach(addImage); // start loading all the images
// This function draws the tiles
function allImagesLoaded(){ /// function that is called when all the images have been loaded
var i, x, y, image;
for(i = 0; i < 256; i += 1){ // loop 16 by 16 times
ctx.drawImage(
images[Math.floor(rand()) % images.length]; //random function does not guarantee an integer so must floor
(i % 16) * tileWidth, // x position
Math.floor(i / 16) * tileHeight, // y position
tileWidth, tileHeight // width and height
);
}
}

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

Canvas not rendering images at all

I have an array of enemies sent from the server and I am recreating them because they were serialized. After, I'm trying to get them to render on the canvas, but that isn't working for some reason.
for (var i = 0; i < enemies.length; i++) { // recreate each enemy and render it
var image = new Image();
var currentFish = new Fish();
for (var key in enemies[i]) { // copying properties to object that has the necessary methods
if (enemies[i].hasOwnProperty(key)) {
currentFish[key] = enemies[i][key];
}
}
image.src = currentFish.icon;
image.onload = function () {
ctx.drawImage(image, currentFish.position.x, currentFish.position.y);
};
ctx.fillText('Drone', 250, 200);
}
I think the issue is that image.onload is not called until after or in between frames so it isn't seen. I'm not sure how to work around this.
Edit:
I forgot to mention that I'm using requestAnimationFrame to handle rendering the canvas, so I don't know when the frame is going to be rendered.
The problem is that your outside for loop is overwriting the image variable with each loop (before the image can be loaded and drawn).
Try this alternative image loader which preloads all images into imgs[] and then calls start():
// image loader
var imageURLs = []; // put the paths to your images here
var imagesOK = 0;
var imgs = [];
imageURLs.push("");
loadAllImages(start);
function loadAllImages(callback) {
for (var i = 0; i < imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function() {
imagesOK++;
if (imagesOK >= imageURLs.length) {
callback();
}
};
img.onerror = function() {alert("image load failed");}
img.crossOrigin = "anonymous";
img.src = imageURLs[i];
}
}
function start() {
// imgs[] holds fully loaded images
// imgs[] is in the same order as imageURLs[]
}

Categories

Resources