I am using the HTML5 canvas to draw an organisational structure. Each node includes an image, the URL of which is obtained form a database. I have a little function that handles the loading of the image, and that adds it to the canvas once loaded. It looks like this (note that the important bit is the onload listener added to the image):
(function (i, ctx, DrawId) {
var img = new Image();
img.src = REPORT.Structures[i].Managers[j].Employee.PhotoUrl; //get URL from object constructed elsewhere
img.onload = function () { //attach onload event
ctx.drawImage(img, x, y, width, height);//draw image on the canvas. x, y width and height are calculated inside the onload, but I have removed the calculations to improve readability
}
}
})(i, ctx, REPORT.DrawId);
This works great, except that the images all pop into existence at random as they finish loading. It looks very untidy.
I am looking for a way to make this look smoother. My first instinct is to try and create a fade in effect, but I can't think of how to do this without redrawing the canvas over and over.
I have thought of trying something like this:
img.onload = function () {
var counter = 1;
var width = 30 * REPORT.ZoomLevel;
var height = 30 * REPORT.ZoomLevel;
function fadeInImage(ctx, img, x, y, width, height) {
ctx.globalAlpha = 0.1 * counter;
ctx.drawImage(img, x, y, width, height);
counter++;
if (counter == 10)
clearTimeout(fadeInImageTimeout);
console.log(counter);
}
var fadeInImageTimeout = setInterval(function () { fadeInImage(ctx, img, x, y, width, height); }, 100);
}
This works, but I am also worried that if it does, it will be too processing heavy.
Has anyone tried doing something like this? Any suggestions would be much appreciated.
PS:
I am currently considering only having one interval that draws all images. Each image will be pushed into an array, and all elements in the array will be drawn once all images are loaded. Will keep you updated.
PPS:
I have managed to get it working as I mentioned in the PS above. It does not seem to affect the performance though. I guess the lag I am still experiencing is related to the image loading time. I'll try and strip out the code into a bare bones version and post it here a bit later, but if anyone can see a flaw in my approach, please do not hesitate to point it out. Again, any suggestions would be greatly appreciated.
Related
Im trying to make a side-scroller game and I got stuck on the running background part. I've looked for solutions and I've discovered some , but they were using javascript not the p5 library.
I started from the tutorials found on The Coding Train , and looked over all the examples and references on their site.
Although I could avoid this by using something else, just for the sake of it being here in case someone gets stuck on the same issue, could anyone offer a solution to this in p5? Disclaimer: Im a total noob p5.js.
later edit : By running background i mean moving Background image in a loop from left to right
Honestly, from the discussion we had in the comments, it sounds like you're overthinking it.
The general approach to animation (that tutorial is for Processing, but the principles apply to P5.js as well) is as follows:
Step 1: Create a set of variables that represent the state of your scene.
Step 2: Use those variables to draw your scene every frame.
Step 3: Change those variables over time to make your scene move.
You already know what to do: load an image that contains your background, then draw that image, and move it a little bit each frame.
You've said you want to call the background() function instead of the image() function, which doesn't make a ton of sense. The background() function is not any more efficient than the image() function. In fact, the background() function just calls the image() function for you!
From the P5.js source:
p5.prototype.background = function() {
if (arguments[0] instanceof p5.Image) {
this.image(arguments[0], 0, 0, this.width, this.height);
} else {
this._renderer.background.apply(this._renderer, arguments);
}
return this;
};
P5.js simply checks whether the argument is an image, and if so, calls the image() function for you. So it doesn't really make sense to say that using the image() function is "less efficient" than using the background() function.
Taking a step back, you should really avoid thinking about these kinds of micro-optimizations until you A: understand the problem and B: actually have a problem. Don't make assumptions about "efficiency" until you've actually measured your code for performance.
Anyway, back to your question. You also said that you're loading the image twice, which you shouldn't have to do. You can just load the image once (make sure you do that in the setup() function and not the draw() function, and then draw that image twice:
var img;
function preload() {
img = loadImage("image.jpg");
}
function setup() {
image(img, 0, 0);
image(img, 100, 100);
}
And since you can draw two images, you'd then just draw them next to each other. Here's an example using colored rectangles to show the approach more clearly:
var offsetX = 0;
function setup() {
createCanvas(200, 200);
}
function draw() {
background(0);
fill(0, 255, 0);
rect(offsetX, 0, width, height);
fill(0, 0, 255);
rect(offsetX + width, 0, width, height);
offsetX--;
if(offsetX <= -width){
offsetX = 0;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.14/p5.js"></script>
There are other ways to do it, like creating an image that contains the wrapping itself. But the general approach is pretty much the same.
If you're still stuck, please try to break your problem down into smaller pieces like I've done here. For example, notice that I created a simple sketch that deals with images, and another simple sketch that deals with moving rectangles. Then if you get stuck, please post a MCVE in a new question post and we'll go from there. Good luck.
Maybe it is a late answer.. but you can make the environment 3D and then move the camera.
Docs: https://p5js.org/reference/#/p5/camera
Example:
function setup() {
createCanvas(windowWidth - 200, windowHeight - 200, WEBGL);
background(175);
frameRate(30);
}
function draw() {
background(175);
//move the camera Xaxis when mouse is moved
let camX = map(mouseX, 0, width, 0,width);
camera(camX, 0, (height/2.0) / tan(PI*30.0 / 180.0), camX, 0, 0, 0, 1, 0);
normalMaterial();
noStroke();
ambientLight(251,45,43);
box(100, 100, 50);
ang += 0.3;
rotateY(ang * 0.03);
}
Keep calm and Happy Coding!
It is known that if you have html elements (for example a modal window with lists) on top of a flash element you have huge performance issues cause flash cause the browser to repaint the any html on top of it while the flash is animating. I wonder if the same happens if you have html elements on top of an animating canvas element.
I am asking this cause I am building a canvas game and I wonder if it is a good idea to make the GUI (menus, navigation buttons etc) using DOM and not drawing it on canvas.
I just tested using Chromium Version 28.0.1500.45 Mageia.Org 3 (205727) and the elements are NOT repainted while my canvas animates.
I tried this simple box animation with a DIV over it. After that, I profiled my application to see what was happening. I noticed only the canvas was being repainted.
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var GX = 0;
function animate() {
var canvas = document.getElementById('jaja');
var context = canvas.getContext('2d');
// update
GX += 10;
if (GX > 500) GX = 0;
// clear
context.clearRect(0, 0, canvas.width, canvas.height);
// draw stuff
context.beginPath();
context.rect(GX, 10, 100, 100);
context.fillStyle = '#8ED6FF';
context.fill();
context.lineWidth = 1;
context.strokeStyle = 'black';
context.stroke();
requestAnimFrame(function() {
animate();
});
} // request new frame
Following, I tried to make the DIV repaint by selecting the text. This time yes, the DIV repainted.
Here's a screenshot of the profile.
1: We can se the Paint (600x586) being called everytime I do my animation.
2: It called Paint for the DIV element ONLY when I selected the text.
I personally do not believe any other browser would have a different behavior than that.
Yes, putting other DOM elements on top of a canvas element will reduce its performance.
This is because the browser have to do extra clipping when updating canvas / painting.
The canvas need to update 60 times per second to output to screen. If something is on top it needs to be clipped just as many times. If the DOM element is repainted as well will be browser dependent but the performance of the canvas element itself is reduced.
Usually the DOM paints happens in a single thread (but is about to change for most major browsers) so if there is extra load on that thread it will affect everything else being drawn too.
And there is the single-threading of JavaScript which is necessary to use to update canvas. If canvas has reduced performance than the script executing its changes (as well as changes to the DOM) will get hit too.
Here's a related image:
I want to achieve something like what's pictured on the right side of my image. But I also have a parent container that has a background image of its own, instead of a solid color.
Any advice?
EDIT: Forgot to add, cross-browser compatibility is important. (Or atleast Firefox).
I can only think of one pure CSS solution and it is simply insane.
Let's say your image has a width of 100px. You'll have to create a div that's 100px wide and give it 100 children that are each 1px wide, that each have the same background (positioned accordingly) and that each have an opacity from 0 (the first child) to .99 (the last child).
Personally, I think it's crazy and I'd never use this method.
Rory O'Kane came with a nice and clean solution and I also have another idea which involves JavaScript.
Basically, the idea is that you use a canvas element (support), draw your image on it, loop through its pixels and adjust the alpha for each.
demo
(scroll down to see the result)
Relevant HTML:
<div class='parent'>
<canvas id='c' width='575' height='431'></canvas>
</div>
Relevant CSS (setting the background image on the parent)
.parent {
background: url(parent-background.jpg);
}
JavaScript:
window.onload = function() {
var c = document.getElementById('c'),
ctxt = c.getContext('2d'),
img = new Image();
img.onload = function() {
ctxt.drawImage(img, 0, 0);
var imageData = ctxt.getImageData(0, 0, 575, 431);
for(var i = 0, n = imageData.data.length; i < n; i += 4) {
imageData.data[i + 3] = 255*((i/4)%575)/575;
}
ctxt.putImageData(imageData, 0, 0);
};
/* images drawn onto the canvas must be hosted on the same web server
with the same domain as the code executing it */
/* or they can be encoded like in the demo */
img.src = 'image-drawn-on-canvas.jpg';
};
check these out maybe helpful
DEMO 1
DEMO 2
Ignoring possible CSS-only methods, you can make the image a PNG with the transparent gradient built in to the image’s alpha channel. All browsers support PNG transparency, except for IE 6 and below. Here’s what your sample image would look like as a PNG with a transparent gradient (try putting this image against other backgrounds):
If the images are user-submitted so you can’t add the gradient ahead of time, you could create and store a gradient-added version of each image at the time that the user uploads them.
CSS only method:
https://gist.github.com/3750808
Am trying to set a "dirty zone" on my canvas to prevent the repainting of unmoved items (background image, static items, etc.)
i.e. only the background painted behind a moving player needs to be redrawn
EDIT: As suggested, here's the jsfiddle of it
http://jsfiddle.net/7kbzj/3/
The "update" method doesn't work out there, so it's moveSprite() you can get run by clicking the "move sprite" link... Basically, the clipping zone shouldmove by 10px to the right each time you click. Clipping mask stays at initial position, only the re-paint occurs. Weird o_O
So as I init my canvas, once the background is painted, set I use the ctx.save() method:
function init() {
canvas = document.getElementById('kCanvas');
ctx = canvas.getContext('2d');
ctx.fillStyle = "rgb(0,128,0)";
ctx.fillRect (0,0,320,240);
ctx.save();
setInterval(function () { update(); }, tpf);
}
In order to see the clipping works, I draw a different color background (blue one) in the area that I wanted clipped... the result is bad, only the first clipped area is painted blue :(
function update() {
setDirtyArea(x,y,w+1,h)
ctx.fillStyle = "rgb(0,0,128)";
ctx.fillRect (0,0,320,240);
x++;
// paint image
ctx.clearRect(x,y,w,h);
ctx.drawImage(imageObj, x, y);
}
function setDirtyArea(x,y,w,h) {
ctx.restore();
// define new dirty zone
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.clip();
}
I'd love to se the blue zone propagate itself towards the right of the screen... please help, I don't understand what's wrong!
Thanks,
J.
You need to wrap the actual drawing and clipping of the box with the save and restore methods. and include the closePath method. I have modified your fiddle to work the way I believe you are trying to make it.
http://jsfiddle.net/jaredwilli/7kbzj/7/
ctx.save(); // save the context
// define new dirty zone
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.clip();
ctx.restore(); // restore the context
I also have learned that using save and restore can get really complex, and confusing to know which context your in. It came up with a pretty huge canvas app im working on, and i found that indenting your canvas code helps immensely. Especially the save/restores. I have even decided it should be considered a best practice, so the more people who know and do it the better.
Hope this helps.
I asked a question on SO about compiling an image file from HTML. Michaël Witrant responded and told me about the canvas element and html5.
I'm looked on the net and SO, but i haven't found anything regarding drawing a misc element's contents onto a canvas. Is this possible?
For example, say i have a div with a background image. Is there a way to get this element and it's background image 'onto' the canvas? I ask because i found a script that allows one to save the canvas element as a PNG, but what i really want to do is save a collection of DOM elements as an image.
EDIT
It doesn't matter what language, if it could work, i'm willing to attempt it.
For the record, drawWindow only works in Firefox.
This code will only work locally and not on the internet, using drawWindow with an external element creates a security exception.
You'll have to provide us with a lot more context before we can answer anything else.
http://cutycapt.sourceforge.net/
CutyCapt is a command line utility that uses Webkit to render HTML into PNG, PDF, SVG, etc. You would need to interface with it somehow (such as a shell_exec in PHP), but it is pretty robust. Sites render exactly as they do in Webkit browsers.
I've not used CutyCapt specifically, but it came to me highly recommended. And I have used a similar product called WkHtmlToPdf, which has been awesome in my personal experience.
After many attempts using drawWindow parameters, that were drawing wrong parts or the element, I managed to do it with a two steps processing : first capture the whole page in a canvas, then draw a part of this canvas in another one.
This was done in a XUL extension. drawWindow will not work in other browsers, and may not work in a non-privileged context due to security reasons.
function nodeScreenshot(aSaveLocation, aFileName, aDocument, aCSSSelector) {
var doc = aDocument;
var win = doc.defaultView;
var body = doc.body;
var html = doc.documentElement;
var selection = aCSSSelector
? Array.prototype.slice.call(doc.querySelectorAll(aCSSSelector))
: [];
var coords = {
top: 0,
left: 0,
width: Math.max(body.scrollWidth, body.offsetWidth,
html.clientWidth, html.scrollWidth, html.offsetWidth),
height: Math.max(body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight)
var canvas = document.createElement("canvas");
canvas.width = coords.width;
canvas.height = coords.height;
var context = canvas.getContext("2d");
// Draw the whole page
// coords.top and left are 0 here, I tried to pass the result of
// getBoundingClientRect() here but drawWindow was drawing another part,
// maybe because of a margin/padding/position ? Didn't solve it.
context.drawWindow(win, coords.top, coords.left,
coords.width, coords.height, 'rgb(255,255,255)');
if (selection.length) {
var nodeCoords = selection[0].getBoundingClientRect();
var tempCanvas = document.createElement("canvas");
var tempContext = tempCanvas.getContext("2d");
tempCanvas.width = nodeCoords.width;
tempCanvas.height = nodeCoords.height;
// Draw the node part from the whole page canvas into another canvas
// void ctx.drawImage(image, sx, sy, sLargeur, sHauteur,
dx, dy, dLargeur, dHauteur)
tempContext.drawImage(canvas,
nodeCoords.left, nodeCoords.top, nodeCoords.width, nodeCoords.height,
0, 0, nodeCoords.width, nodeCoords.height);
canvas = tempCanvas;
context = tempContext;
}
var dataURL = canvas.toDataURL('image/jpeg', 0.95);
return dataURL;
}