I use this code to preload an image:
var image = new Image();
image.addEventListener("load", loadHandler, false);
image.src = "someImage.png";
function loadHandler()
{
// the programme continues
}
Lets say the image refers to a certain button that occurs in the middle of a game.
The problem is that the code waits for a few seconds when the image should be rendered for the first time (using drawImage function). All the other usage (means the image should be shown another time) are instant.
The image is a sprite sheet, size about 3 MB. Everything on a local computer.
If I understand properly your issue, it's not about loading but more about rendering the sprite (which is quite big).
It might help to insert it into the document (wherever you want) and then wait for it to load before doing anything else.
You can hide it with the help of opacity, visibility, etc. properties (any choice should do the trick).
If your target browsers are supporting it, you could also use instead link prefetching.
Related
Background
I'm making a simple online drawing app so that I can practice my JS and canvas skills. It's going really well, with the ability to draw freely, and also the ability to draw straight lines.
How my drawing app works
It registers mouse and touch events, which handle all of the drawing and saving.
Here's some example code:
var tools={
"pencil":{
"started": false,
... (some data used by the pencil tool),
"start":function(e){
if(currentTool!=="pencil")return;
// Make sure that the selected tool on the toolbar is the pencil
// code for mousedown/touchstart
tools.pencil.started=true;
},
"move":function(e){
if(!tools.pencil.started)return;
// code for mousemove/touchmove
},
"end":function(e){
if(!tools.pencil.started)return;
// code for mouseup/touchend
tools.pencil.started=false;
}
},
"other tools":{
...
}
};
// And here would be a function which adds the mouse and touch events
Here is my pencil tool:
var tools={
pencil:{
started:false,
start:function(e){
if(currentTool!=="pencil")return; // Make sure the pencil tool is selected
e.preventDefault();
e=e.clientX?e:e.touches[0]; // Allow touch
context.beginPath(); // Begin drawing
context.moveTo(e.clientX-50,e.clientY); // Set the position of the pencil
tools.pencil.started=true; // Mark the pencil tool as started
},
move:function(e){
if(tools.pencil.started){ // Make sure the pencil is started
e.preventDefault();
e=e.clientX?e:e.touches[0];
context.lineTo(e.clientX-50,e.clientY); // Draw a line
context.stroke(); // Make the line visible
}
},
end:function(e){
if(tools.pencil.started){
e.preventDefault();
//tools.pencil.move(e); // Finish drawing the line
tools.pencil.started=false; // Mark the pencil tool as not started
}
}
}
};
Ignore the -50 parts (they're just to adjust with the sidebar). This works, but doesn't save to localStorage.
Problem
TL;DR: I need to save everything on the canvas into some storage (I was currently using localStorage but anything would work, although I would prefer usage of the client-side only). I can't figure out how to efficiently store it, where 'efficiently' means both fast and accurate (accurate as in it stores the whole line). Lines can be stored, but hand-drawn items I haven't figured out yet.
Explanation:
When the user resizes the window, the canvas resizes to the window size (I made this happen, not a bug). I was able to make it that when you resize, it first saves the drawings onto a temporary canvas and then, after the main canvas resizes, redraws them back. But there's a problem. Here's an example to make it clear:
You open the draw app and fill the screen with drawings.
You open DevTools and some of the drawings get covered
When you close the DevTools, then those drawings are gone.
The reason is because since the canvas got smaller, the drawings that went off of it were lost, and when it came back to the original size, they weren't visible (because they're gone). I decided to save everything to localStorage so that I can retain them (and also for some more features that I may add). The lines are working (since they only need to say, "I am a line, I start at x,y and end at x,y". That's it for a line of any size. But for hand-drawn images, they can go anywhere, so they need to say, "I am a pixel, I am at x,y", but many times for even remotely complex images.
Attempted solution
Whenever they move the mouse, it saves to a variable which then updates to localStorage. The problems with this is that if you go fast, then the lines isn't complete (has holes in it) and localStorage is storing a lot of text.
Question
How can I efficiently store all of the user's hand-drawn (pencil tool) images (client-side operations preferred)?
My suggestion is to consider the target use of this app and work backwards from it.
If you're doing something like a MS Paint drawing app, storing a canvas as a bitmap may be better. To deal with cropping issues, I would suggest that you use a predefined canvas size or a setup stage (with a maximum size to prevent issues of saving to LocalStorage).
If you're worried about image size being restrictive due to memory cap, you can also utilize compression.
ie.
canvas.toDataURL('image/jpeg', 0.5);
👆 will compress the image and return a string that will be localStorage friendly
If, on the other ✋, you are building something more like an excalidraw app, where you want to maintain the ability to edit any drawn object, then you'll need to store each object individually. Whether you use canvas or SVG doesn't really matter imho, but the SVG can make it a bit easier in the sense that the definition of how to store each element is already established, so you don't need to re-invent the wheel, so-to-speak, just need to implement it. Use of SVG doesn't necessarily preclude you from using canvas, as there are a couple SVG-in-canvas implementation, but if you're doing this as a learning project (especially for canvas), it's likely not the route you want to take.
I have an animated gif in an img tag that I start by rewriting the src attribute. The gif was created, though, to loop and I only want it to play once. Is there a way, with Javascript or jQuery, to stop an animated gif from playing more than once?
I was having the same problem with an animated gif. The solution is rather simple.
Open the Animated gif in Photoshop.
Go to the Window tab and select timeline(if the timeline is not already open).
At the bottom of the timeline panel, you will find an option, which says "Forever".
Change that to "Once".
Go to File> Export> Export for Web and save it as a gif.
That should do it.
can you find out how long the gif takes to loop once?
if so then you can stop the image like this:
pseudocode:
wait until the end of the image (when it is about to loop)
create a canvas element that has a static version of the gif as currently displayed drawn on it
hide gif
display canvas element in a way that makes it look like the gif froze
javascript:
var c = $("canvas")[0];
var w = c.width;
var h = c.height;
var img = $("img")[0];
setTimeout(function () {
c.getContext('2d').drawImage(img, 0, 0, w, h);
$(img).hide();
$(c).show();
},10000);
jsfiddle
edit:
I forgot to add reference to the original answer that I took this from, sorry
Stopping GIF Animation Programmatically
that one doesn't address the time factor you need for only one loop
Also, it has been mentioned that this approach is problamatic in certain cases (It actually didn't work when I try it in firefox right now...). so here are a few alternatives:
mentioned by Mark: edit the gif itself to avoid looping. this is the best option if you can.
but I've run into cases where it was not an option (like automated generation of images by a third party)
instead of rendering the static image with canvas, keep a static image version and switch to stop looping . this probablyhas most of the problems as the canvas thing
Based on this answer, it's kinda expensive, but it works. Let's say a single loop takes 2 seconds. At a setTimeout after 2 seconds kick in a setInterval, that would reset image source every millisecond:
setTimeout(function() {
setInterval(function() {
$('#img1').attr('src',$('#img1').attr('src'))
},1)
}, 2000)
again, probably just a proof of concept, but here's demo: http://jsfiddle.net/MEaWP/2/
Actually it is possible to make a gif to stop after just one iteration or any specific number of iterations, see an example below (if it is not already stopped), or in jsfiddle.
To do that the gif must be created with number of iterations specified. This could be done using Screen to Gif, it allows to open a gif or a bunch of images and edit it frame by frame.
This solution also allows you to reset the animation by imgElem.src = imgElem.src; but this does not work in MS IE/Edge.
Jurijs Kovzels's answer works in some condition but not in all.
This is browser-dependent.
It works well with Firefox. But In Google Chrome and Safari, it does not work if the gif is on the same server. The example he provided works because the gif is on the external server.
To restart gifs stored on the internal server, using Google Chrome and Safari, you need extra steps to make it work.
const img = document.getElementById("gif");
img.style = "display: none;";
img.style = "display: block;";
setTimeout(() => {
img.src = img.src;
}, 0);
This is inspired by this answer.
Not sure if this is the best way to respond to everyone and have it appear after all the previous answers and comments, but it seems to work.
I don't have much control over the gif. People post whatever gif they want as the "thankyou.gif in their account directory and then the ThankYou code runs whatever they've put there when a comment is submitted to a form they've posted. So some may loop, some may not, some may be short, some may be long. The solution I've come to is to tell people to make them 5 seconds, because that's when I'm going to fade them out, and I don't care if they loop or not.
Thanks for all the ideas.
I know I am pretty late here but..here it is...
I don't know if you would go to this length but let me share a trick.
Open the GIF in Macromedia Flash 8(it has deprecated since then), Export the GIF as Animated GIF. You will have to choose the file location. After that you would receive a dialog box with settings. In that, add the number of times you want the animation to happen. Click OK. Problem solved.
I am trying to convert the canvas element on this page to a png using the following snippet (e.g. enter in JavaScript console):
(function convertCanvasToImage(canvas) {
var image = new Image();
image.src = canvas.toDataURL("image/png");
return image;
})($$('canvas')[0]);
Unfortunately, the png I get is completely blank. Notice also that the original canvas goes blank after resizing the page.
Why does the canvas go blank? How can I convert this canvas to a png?
Kevin Reid's preserveDrawingBuffer suggestion is the correct one, but there is (usually) a better option. The tl;dr is the code at the end.
It can be expensive to put together the final pixels of a rendered webpage, and coordinating that with rendering WebGL content even more so. The usual flow is:
JavaScript issues drawing commands to WebGL context
JavaScript returns, returning control to the main browser event loop
WebGL context turns drawing buffer (or its contents) over to the compositor for integration into web page currently being rendered on screen
Page, with WebGL content, displayed on screen
Note that this is different from most OpenGL applications. In those, rendered content is usually displayed directly, rather than being composited with a bunch of other stuff on a page, some of which may actually be on top of and blended with the WebGL content.
The WebGL spec was changed to treat the drawing buffer as essentially empty after Step 3. The code you're running in devtools is coming after Step 4, which is why you get an empty buffer. This change to the spec allowed big performance improvements on platforms where blanking after Step 3 is basically what actually happens in hardware (like in many mobile GPUs). If you want work around this to sometimes make copies of the WebGL content after step 3, the browser would have to always make a copy of the drawing buffer before step 3, which is going to make your framerate drop precipitously on some platforms.
You can do exactly that and force the browser to make the copy and keep the image content accessible by setting preserveDrawingBuffer to true. From the spec:
This default behavior can be changed by setting the preserveDrawingBuffer attribute of the WebGLContextAttributes object. If this flag is true, the contents of the drawing buffer shall be preserved until the author either clears or overwrites them. If this flag is false, attempting to perform operations using this context as a source image after the rendering function has returned can lead to undefined behavior. This includes readPixels or toDataURL calls, or using this context as the source image of another context's texImage2D or drawImage call.
In the example you provided, the code is just changing the context creation line:
gl = canvas.getContext("experimental-webgl", {preserveDrawingBuffer: true});
Just keep in mind that it will force that slower path in some browsers and performance will suffer, depending on what and how you are rendering. You should be fine in most desktop browsers, where the copy doesn't actually have to be made, and those do make up the vast majority of WebGL capable browsers...but only for now.
However, there is another option (as somewhat confusingly mentioned in the next paragraph in the spec).
Essentially, you make the copy yourself before step 2: after all your draw calls have finished but before you return control to the browser from your code. This is when the WebGL drawing buffer is still in tact and is accessible, and you should have no trouble accessing the pixels then. You use the the same toDataUrl or readPixels calls you would use otherwise, it's just the timing that's important.
Here you get the best of both worlds. You get a copy of the drawing buffer, but you don't pay for it in every frame, even those in which you didn't need a copy (which may be most of them), like you do with preserveDrawingBuffer set to true.
In the example you provided, just add your code to the bottom of drawScene and you should see the copy of the canvas right below:
function drawScene() {
...
var webglImage = (function convertCanvasToImage(canvas) {
var image = new Image();
image.src = canvas.toDataURL('image/png');
return image;
})(document.querySelectorAll('canvas')[0]);
window.document.body.appendChild(webglImage);
}
Here's some things to try. I don't know whether either of these should be necessary to make this work, but they might make a difference.
Add preserveDrawingBuffer: true to the getContext attributes.
Try doing this with a later tutorial which does animation; i.e. draws on the canvas repeatedly rather than just once.
toDataURL() read data from Buffer.
we don't need to use preserveDrawingBuffer: true
before read data, we also need to use render()
finally:
renderer.domElement.toDataURL();
I have a asp.net MVC application where I display a number of image in a view, the number of these images rang between 100 to 2500 per group. On the view I preload the images for each group, then I select a different group from a dropdownlist, this will refresh the page. First preload works great, however, when I change the group it is very slow. The reference to these images are in a MS SQL database, the list of images for a particular gorup comes throught the controller (C#). Here is the Javascript I am using:
var impath = $("#ImagePath").val();
var im = $("#ImageName").val();
var images = null;
var images = [];
var tochar = $("#lastchar").val();
function preload_images() {
var i;
for (i = tochar; i < sl; i++) {
images[i] = new Image();
var name = impath.toString() + im.toString() + i;
}
}
My question is there is a way to speed up the preload process, and do I need to remove the preloaded images from the memory before loading the next group and how can I delete them, I used images=null, is this is the right way. Thanks in advance.
The best way to handle that many images would be using sprites. With sprites, you load one larger image that contains many, many smaller images. Since loading a single image is lot, lot faster in the browser than loading a lot of small images, your preloading can be a lot faster. In fact, you could literally just preload one image that contained a whole block of your sub-images.
Then, when you want to display one of those images, you display only a clipped portion of the image. There are a number of ways to do that display, but a common way is creating an object of the desired cell size and displaying the larger image as the background with the appropriate offset to line up with the desired part of the larger image.
Here's a useful reference on CSS and sprites: http://css-tricks.com/css-sprites/
You do have to create the appropriate sprites on the server side to use this technique.
In answer to some of your other questions.
No, you can't speed them up any more other than loading fewer images (as suggested above) or reducing the size of each image. The browser will only make N image requests at a time (likely to avoid overloading servers) over the network so the rest will be queued up until one finishes and then the next request will be made, etc...
You can release images from memory by just making sure that you aren't holding a reference to them any more. That way, the browser garbage collector will clean up the Image() objects that you had created. In your code setting images = null or images = [] will accomplish that as long as those Image() objects aren't referenced anywhere else. The images will still remain in the browser's cache and will still essentially be preloaded (they won't have to be loaded over the network if referenced again).
I've been working on trying to make some visual effects for a javascript game I'm creating, and I noticed a piece of code that I'm using to modulate the color of my sprites makes my browsers memory usage go up and up, seemingly without limit.
You can see the code and the memory leak here: http://timzook.tk/javascript/test.html
This memory leak only happens in my updateimage() function when I call getImageData from my canvas context every frame (via setInterval) in order to make a new ImageData object to recolor. I would have thought that javascript's garbage collector would be destroying the old one, but if not I have no idea how to destroy it manually. Any help figuring out why it does this or how to fix it would be appreciated.
My question is very similar to this one: What is leaking memory with this use of getImageData, javascript, HTML5 canvas However, I NEED my code to run every frame in the functioned called by setInterval, his solution of moving it outside the setInterval function isn't an option for me, and I can't leave a comment asking if he found some other method of solving it.
Note to people testing it out: Since this example uses getImageData, it can't be tested out locally just by throwing it in a .html file, a web server is required. Also it obviously uses HTML5 elements so some browsers won't work with it.
Edit: *SOLVED* Thanks, the solution below fixed it. I didn't realize that you could use a canvas element the same way as you could use an image in drawImage(), I restructured my code so it now uses significantly less memory. I uploaded this changed code to the page linked above, if anyone wants to see it.
You aren't getting a memory leak from your calls to getImageData(). The source of your problem is this line:
TempImg.src = ImgCanvas.toDataURL("image/png");
Effectively, every time that line of code is executed the browser "downloads" another image and stores it in memory. So, what you actually end up with is a cache that is growing very quickly. You can easily verify this by opening the site in Chrome and checking the resources tab of the developer tools (ctrl+shift+i).
You can work around this by making a TempImgCanvas and storing your image data on that canvas instead of keeping an image object updated after every call to your updateimage() loop.
I have to step away for a while, but I can work up an example in a few hours when I get back, if you would like.
Edit: I restructured things a bit and eliminated your caching issue. You just have to make two changes. The first is replacing your updateimage() function with this one:
function updateimage() {
var TempImgData = ImgContext.getImageData(0, 0, ImgCanvas.width, ImgCanvas.height);
var NewData = TempImgData.data;
var OrigData = ImgData.data;
//Change image color
var len = 4*ImgData.width*ImgData.height-1;
for(var i=0;i<=len;i+=4) {
NewData[i+0] = OrigData[i+0] * color.r;
NewData[i+1] = OrigData[i+1] * color.g;
NewData[i+2] = OrigData[i+2] * color.b;
NewData[i+3] = OrigData[i+3];
}
//Put changed image onto the canvas
ImgContext.putImageData(TempImgData, 0, 0);
}
The second is updating the last line in draw() to read as follows:
drawImg(ImgCanvas, Positions[i].x, Positions[i].y, Positions[i].x+Positions[i].y);
Using this updated code, we simply refer to the original base (white) image data and calculate new values based on that every time we go through the updateimage() function. When you call getImageData() you receive a copy of the image data on the canvas, so if you edit the canvas or the data, the other one remains untouched. You were already grabbing the original base image data to begin with, so it made sense to just use that instead of having to re-acquire it every time we updated.
Also, you'll notice that I modified your loop that changes the image color slightly. By obtaining a handle to the actual data array that you want to access/modify, you save yourself having to resolve the array location from the parent object every time you go through the loop. This technique actually results in a pretty nice performance boost. Your performance was fine to start with, but it can't hurt to be more efficient since it's pretty straight-forward.