How to make pre-initialized array contents work, vs. array.push()? - javascript

Why can't the images be defined in an array as shown here.
Why is it necessary push a new Image object in the array every time?
var canvas = null;
var ctx = null;
var assets = [
'/media/img/gamedev/robowalk/robowalk00.png',
'/media/img/gamedev/robowalk/robowalk01.png',
'/media/img/gamedev/robowalk/robowalk02.png',
'/media/img/gamedev/robowalk/robowalk03.png',
'/media/img/gamedev/robowalk/robowalk04.png',
'/media/img/gamedev/robowalk/robowalk05.png',
'/media/img/gamedev/robowalk/robowalk06.png',
'/media/img/gamedev/robowalk/robowalk07.png',
'/media/img/gamedev/robowalk/robowalk08.png',
'/media/img/gamedev/robowalk/robowalk09.png',
'/media/img/gamedev/robowalk/robowalk10.png',
'/media/img/gamedev/robowalk/robowalk11.png',
'/media/img/gamedev/robowalk/robowalk12.png',
'/media/img/gamedev/robowalk/robowalk13.png',
'/media/img/gamedev/robowalk/robowalk14.png',
'/media/img/gamedev/robowalk/robowalk15.png',
'/media/img/gamedev/robowalk/robowalk16.png',
'/media/img/gamedev/robowalk/robowalk17.png',
'/media/img/gamedev/robowalk/robowalk18.png'
];
var frames = [];
var onImageLoad = function() {
console.log("IMAGE!!!");
};
var setup = function() {
j=0;
body = document.getElementById('body');
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
canvas.width = 100;
canvas.height = 100;
body.appendChild(canvas);
for (i = 0; i <= assets.length - 1; ++i) {
frames[i].src = assets[i];
}
setInterval(animate,30);
}
var animate = function() {
ctx.clearRect(0,0,canvas.width,canvas.height);
if (j >= assets.length) {
j=0;
}
var image = new Image();
image.src = frames[j];
ctx.drawImage(image,0,0);
++j;
}

The first reason is to reduce latency. Putting only the URLs into an array means that images have not been pre-fetched before the animation starts. The first round of animation is going to be slow and jerky as each image is retrieved from the net. If the animation is repeated, the next round will be faster. This consideration mostly applies to animations which replaced image elements on the page (in the DOM) rather than by writing to a canvas.
The second reason is to remove overhead and improve efficiency in the animation loop. Using new Image() inside the loop means that drawing time for each frame includes the time taken to create a new Image object as well as draw it on the canvas. In addition the image content can only be written to the canvas after it has been fetched, making it necessary to write to the canvas from an onload handler attached to the image object. The posted code does not do this and could throw an error in some browsers trying to synchronously write an image with no data to the canvas. Even if otherwise successful, repeated animations would be creating a new Image object each time a frame is displayed and churning memory usage.
Note the original version probably used onImageLoad to check when the image has been fully loaded from the web before pushing the object into an array of preloaded image objects. This is the preferred method of prefetching animation images.
And don't forget to define j before use :-)

Related

SVG to canvas Image working half of the time

I want to compare two svg paths (user and model) at some point. The idea is to transform each of them onto ImageData to be able to make pixel comparisons. The problem I have is using the drawImage which leads me to an empty canvas half of the time.
let modelCanvas = document.createElement("canvas");
let modelContext = modelCanvas.getContext("2d");
modelCanvas.width = 898;
modelCanvas.height = 509;
document.body.appendChild(modelCanvas);
let modelImg = new Image(898, 509);
modelImg.src = 'data:image/svg+xml;base64,PHN2ZyBjbGFzcz0ic3ZnLW[....]';
modelContext.drawImage(modelImg, 0, 0, 898, 509);
The code is pretty straightforward and always run without producing error. Still drawImage seems to fail silently times to times.
Here is the JSFiddle (with the full data string) :
https://jsfiddle.net/Ldgpuo03/
Thank you very much for your help.
Image loading by web browser is an asynchronous operation.
You are trying to call modelContext.drawImage when the image is not guaranteed to be loaded.
You must place your drawing code inside the image.onload callback function
This function will be called once when the image loading is fully finished.
let modelCanvas = document.createElement("canvas");
let modelContext = modelCanvas.getContext("2d");
modelCanvas.width = 40;
modelCanvas.height = 40;
document.body.appendChild(modelCanvas);
let modelImg = new Image();
modelImg.src = 'https://i.stack.imgur.com/EK1my.png?s=48';
modelImg.onload = function(){
modelContext.drawImage(modelImg, 0, 0, 40, 40);
}

Pong game in p5.js - background image not loading properly

I'm using a javascript framework called p5. I'm trying to set the background of my Pong game to an image I found online. I followed all references I could find to try to get it to work, but for some reason the background doesn't update itself. I end up getting a line of chickens (the ball of my game). The only part of the background that seems to work properly is the top left corner.
var sticks = [];
var ball;
var wallDis = 50;
// var imgs = [];
var score = [];
function preload(){
chick = loadImage('images/chick.png');
farm = loadImage('images/Farm.jpg');
}
function setup(){
createCanvas(600, 600);
sticks[0] = new Stick([enter image description here][1]wallDis);
sticks[1] = new Stick(width-wallDis);
ball = new Ball(chick);
score[0] = new ScoreBoard(width/3, 50);
score[1] = new ScoreBoard(width*2/3, 50);
}
function draw(){
background(farm);
// resizeCanvas(img.width, img.height);
for(var i =0; i<sticks.length; i++){
sticks[i].move();
sticks[i].show();
}
ball.move();
ball.show();
... etc
The background() function doesn't stretch the image to fit the size of the canvas. From the reference, emphasis mine:
p5.Image: image created with loadImage() or createImage(), to set as background (must be same size as the sketch window)
That's why you're seeing the image in the upper-left corner.
To fix your problem, just resize the image to be the same size as your sketch. You can do that ahead of time, or there are handy functions in the reference you could use as well.

Fastest way to change image pixels before rendering on an HTML5 canvas

I have a (largish) HTML5 canvas. Its rendering a pictures from a file, using context.drawImage() and this is quite fast. (Note that there are more than one picture on the same canvas).
Now I need to perform some manipulations to the pixels on the canvas, basically I need to perform Alpha Blending which darkens certain areas of the picture. So instead I used this approach.
//create an invisible canvas so that we don't do the actual rendering of the image
var invisibleCanvas = document.createElement('canvas');
invisibleCanvas.width = myWidth;
invisibleCanvas.height = myHeight;
var invContext = invisibleCanvas.getContext('2d');
invContext.drawImage(imageObj, 0, 0, invisibleCanvas.width, invisibleCanvas.height);
var imageData = invContext.getImageData(0, 0, invisibleCanvas.width, invisibleCanvas.height)
var pixelComponents = imageData.data;
var dkBlendingAmount = 0.5;
for (var i = 0; i < pixelComponents.length; i += 4)
{
//there are a few extra checks here to see if we should do the blending or not
pixelComponents[i] = pixelComponents[i] * dkBlendingAmount;
pixelComponents[i+1] = pixelComponents[i+1] * dkBlendingAmount;
pixelComponents[i+2] = pixelComponents[i+2] * dkBlendingAmount;
}
//this is the real place where I want it
context.putImageData(imageData, xOffset, yOffset);
Is there a way to make this faster? Is there a way to get the image data directly from my imageObj rather than having to put it on a canvas, get the data, convert it and put it on another canvas?

draw preloaded image into canvas

Once again, completely out of my depth but I need to preload some images and then add them to the page when 'all elements (including xml files etc.)' are loaded. The images and references are stored in an array for later access. Trying to draw and image from that array throws an error yet I know it is available as I can just appendTo the page:
preloadImages: function (loadList, callback) {
var img;
var loadedFiles = [];
var remaining = loadList.length;
$(loadList).each(function(index, address ) {
img = new Image();
img.onload = function() {
--remaining;
if (remaining <= 0) {
callback(loadedFiles);
}
};
img.src = loadList[index];
loadedFiles.push({file: 'name of image to be loaded', image: img }); //Store the image name for later refernce and the image
});
}
//WHEN CERTAIN OTHER CONDITIONS EXIST I CALL THE FUNCTION BELOW
buildScreen: function ( imageLocs, image){
//THIS FUNCTION LOOPS THROUGH imageLocs (XML) AND CREATES CANVAS ELEMENTS, ADDING CLASSES ETC AND DRAWS PART OF A SPRITE (image)
//INTO THE CANVASES CREATED
var ctx = $('ID of CANVAS').get(0).getContext("2d");
var x = 'position x in imageLocs'
var y = 'position y in imageLocs'
var w = 'width in imageLocs'
var h = 'position x in imageLocs'
ctx.drawImage(image, x,y, w, h, 0, 0, w, h); //THIS THROWS AN ERROR 'TypeError: Value could not be converted to any of: HTMLImageElement, HTMLCanvasElement, HTMLVideoElement'
//$(image).appendTo("#innerWrapper") //YET I KNOW THAT IT IS AVAILABE AS THIS LINE ADDS THE IMAGE TO THE PAGE
}
Problem
The issue is caused because you are passing a jQuery object to a native function, in this case ctx.drawImage, drawImage will only support native objects.
startSequence : function(){
$('#innerWrapper').empty();
var screenImageRef = $.grep(ST.imageFilesLoaded, function(e){
return e.file == 'AtlasSheet'
});
var screenImage = $(screenImageRef[0].image);
var imageLocsRef = $.grep(ST.xmlFilesLoaded, function(e){
return e.file == 'IMAGELOCS'
});
var imageLocs = $(imageLocsRef[0].xml);
//$(screenImage).appendTo("#innerWrapper") //appends screenImage
Utilis.buildScreen('1', imageLocs, screenImage, ST.didYouSeeIt, 'ST')
}
Your screenImage var is created by $(screenImageRef[0].image), this will return a jQuery object that wrappers the native image object. To get back to the original native image object use the following:
screenImage.get(0)
or
screenImage[0]
The former is the jQuery supported way.
Solution
So the fix to your code should be either, changing the following line:
Utilis.buildScreen('1', imageLocs, screenImage.get(0), ST.didYouSeeIt, 'ST');
Or changing the line in the buildScreen method:
ctx.drawImage(image.get(0), x,y, w, h, 0, 0, w, h);
... Whichever you prefer.
Confusion when debugging
The reason why everything appears to work when you append the image, is because you are using jQuery to append the image, and jQuery supports being passed jQuery wrapped elements. If you had tried to append your screenImage using native functions i.e. Element.appendChild() you would have got similar errors.
Just to help in future, it's always best to use console.log to find out what type/structure a variable actually has. Using console.log on your previous image var would have given a strange object dump of the jQuery wrapper (which might have rang alarm bells), rather than the expected [object HTMLImageElement] or some other image/console related output (depending on the browser).
I think your image preloader isn't quite correct as it uses the same img variable for all images.
Here is one that I know works well: https://gist.github.com/eikes/3925183

Chrome Extension img.onload function continuously executes

I'm writing a Chrome extension that blocks possibly offensive content. One method that I am implementing is to scan all the images and see how much skin is showing. I create a new image object, set the crossOrigin flag to " ", then make a onload function that will draw the image onto the canvas, read the data from the canvas, and then perform the analysis, setting a boolean flag for the calling function. After defining the onload function, I assign a src to my image node from my list of sources from the webpage.
The image_scanner function is called inside of a for loop that is looping through each image node on the webpage and performing various operations to block on. This is the last operation that I perform. Here is the code that calls image_scanner:
if (image_scanner(options.scanner_sensitivity, images[i]))
{
// Replace the image with a blank white image
images[i].src = chrome.extension.getURL("replacement.png");
}
Here is the image_scanner function
function image_scanner(sensitivity, image)
{
// Sensitivity is a number and image is an image node.
// Declare a variable to count the number of skin pixels
var skin_count = 0;
if (image.width == 0 && image.height ==0)
{
// This means the image has no size and we cannot block it.
return false;
} // end if
var return_value = null; // set bool flag
// Create an HTML5 canvas object.
var canvas = document.createElement('canvas');
//window.alert("Created Canvas."); // used for testing.
// Get the context for the canvas.
var context = canvas.getContext("2d"); // This is what we actually use to draw images and pull the data from them.
context.canvas.width = image.width; // Set the canvas width to the width of the image
context.canvas.height = image.height; // Set the canvas height to the height of the image
img = new Image(); // Create a new image node to circumvent cross-domain restrictions.
img.crossOrigin = " "; // Set crossOrigin flag to ' ' so we can extract data from it.
img.onload = function(){
window.alert(img.src); // This always gives the same src until Chrome ends the function
context.drawImage(this, 0,0); // Draw the image onto the canvas.
var pixels = context.getImageData(0, 0, image.width, image.height).data;
// Now pixels is an array where every four entries in the array is the RGBa for a single pixel.
// So pixels[0] is the R value for the first pixel, pixels[1] is the G value for the first pixel,
// pixels[2] is the B value for the first pixel, and pixels[3] is the a (alpha or transparency) value for the first pixel.
// This means that pixels.length/4 is the number of pixels in the image.
// Now we calculate the number of skin pixels we can have before blocking the image.
var limit = ((pixels.length)/4) * (sensitivity/100);
// Now we go through the array of pixel data, checking if each pixel is a skin colored pixel based on its RGB value (the first 3 entries for that pixel in the pixels array)
// Each time we find a skin colored pixel, we increment skin_count and check if skin_count >= limit. If so, we return true.
for (var i = 0; i < pixels.length; i += 4) // We go up by four since every four entries describes 1 pixel
{
// pixel is skin if 0 <= (R-G)/(R+G) <= .5 and B/(R+G) <= .5 pixels[i] is the R value, pixels[i+1] is the G value, and pixels[i+2] is the B value.
if ((0 <= ((pixels[i] - pixels[i+1])/(pixels[i] + pixels[i+1]))) && (((pixels[i] - pixels[i+1])/(pixels[i] + pixels[i+1])) <= 0.5) && ((pixels[i+2]/(pixels[i] + pixels[i+1])) <= 0.5))
{
skin_count++;
//window.alert("Found skin pixel."); // used for testing.
if (skin_count >= limit)
{
//window.alert("Blocking image with src: " + image.src); // used for testing.
img.onload = null; // try to clear the onload function
return_value = true;
return false;
} // end inner if
} // end outer if
} // end for loop
//var temp;
img.onload = null;
return_value = false;
return false;
}; // end onload function
img.src = image.src; // Set the new image to the same url as the old one.
return return_value;
} // end image_scanner
I'm not sure what the problem is, but the onload function will run, go through the pixels, set the flag, return, and then run again. I've tried debugging in Chrome's debugger, and that's all I could find. I've tried setting the onload to null inside of the onload function, but it doesn't work. I've tried returning false from the onload function. I've tried waiting in the image_scanner function until return_value != null, but that just seemed to enter an infinite loop and I never even got the alert from the onload function. If anyone has any idea why the onload function will repeatedly execute, I would be very grateful.
If you're going to set .src to the blank image and don't want .onload to get called again when that loads, then you should clear .onload before you set .src.
if (image_scanner(options.scanner_sensitivity, images[i])) {
// clear our onload handler
images[i].onload = function() {};
// Replace the image with a blank white image
images[i].src = chrome.extension.getURL("replacement.png");
}
It also looks like you're returning a value from the onload handler and expecting that value to get returned from the image_scanner function. It doesn't work that way. The onload handler gets called some significant time later, long after image_scanner has already returned. You will need to rewrite your code to work with the asynchronous handling of onload.

Categories