How can I efficiently store hand-drawn lines(canvas)? - javascript

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.

Related

Is there any way to find the color of a pixel in JavaScript WITHOUT using getImageData()?

I'm making a small online multiplayer game with JavaScript and the native Canvas API (WebGL). I want to find the color of pixels on the screen as a form of collision detection, I figure it'd save resources to not have to process every shape every frame, but rather to simply check if the color of a pixel at a certain position is such that it is contacting a shape. (I hope that makes sense)
I ran some tests and I have an average frame delay of about 4-5 milliseconds without collision detection, and then when I make a single call to my canvas context's .getImageData() method, suddenly that frame delay shoots up to 19-20 milliseconds...
As far as I can find online getImageData() is the only means of checking the color of a given pixel, but I have to think there's some other way that doesn't introduce such a huge amount of lag.
I tried running getImageData() on a small section of the screen vs larger sections, and a 1x1 pixel request introduces 10ms latency, where a 600x600 pixel request is about 15ms... So the issue isn't the amount/size of the request, but rather just the request itself is extremely slow, so there's no potential for optimization here, I NEED another way.
Also, caching the image data is also not an option. I need to poll these pixels every single frame, I can't cache it (because the player and the object it needs to collide with are all constantly moving, and they're being controlled over the internet so there's also no way of predicting where they'll be at any given time... I NEED to poll every frame with no exceptions)
To be clear, I'm not asking how to write collisions or how to make pixel-perfect collision detection systems... I'm asking ONLY how to get the color of a pixel on the canvas without having to use .toImageData() because .toImageData() is far too slow for my use case.
Standard collision detection may end up being a better option. Pixel-perfect checking works well for perfect collision detection with complex objects but can get expensive with lots of pixels.
What I would probably recommend instead is to use standard collision detection with simple shapes. If done right, it should have good performance, even for a more complex game.
If you really do want to use pixel-perfect collision, you'll want to check the rectangular bounding boxes of the two objects first to ensure they aren't far away from each other. If their bounding boxes intersect, you could then use bitmasks of your sprites to quickly check each pixel for overlap. This question has a bit more info. It's not JS, but the concept would be the same.

How to antialias strokes drawn in canvas without using clearRect

I am working on a drawing app as a personal project (currently living at http://draw.ist/) and I'd like to get the drawn strokes smooth around the edges but it seems the only way to accomplish this is using clearRect. However, clearRect also clears the canvas every time I start a new path and I'd like to prevent that so the canvas actually accumulates the strokes.
I've seen stuff about creating a secondary canvas to save strokes to or something so the clear doesn't wipe them (at least I think it works somewhat like that) however that method would require a lot of additional code that I'd like to avoid. Most recently I've tried saving the canvas as an imageURL on mouseup and then re-placing it with drawImage right after every clearRect, and this seems to do the trick nicely but it has some bizarre interactions with my eraser and straightline tools, as well as some flickering every time it loads in the saved canvas image.
There isn't necessarily any particular snippet of relevant code here, but for reference the entire site can be found at http://draw.ist/ (Controls: hold shift to draw straight lines, hold spacebar to erase, scroll mousewheel to change brush size)
I'd like all drawn lines to be smooth and have antialiasing but without using the "secondary canvas as memory" method IF POSSIBLE. I think what could help me here as well is understanding why exactly clearRect is necessary to apply antialiasing to strokes in canvas and how it works exactly in layman's terms. I greatly appreciate any assistance on this, whether it's actually helpful or not!

KineticJS clearRect equivalent

I am now entering Kinetic and it has made it far easier for me to draw on canvas. However, building a game app, I need to clear the rectangle on every animation request. They're controlled by an fps cap script, but still, there are about 50 updates per second.
Kinetic's .removeChildren() method not only clears the canvas, it deletes the canvas node from the DOM. Doing so not only makes DOM queries inconsistent by intervals of .02 second, but also drops my FPS rate by about 60% in comparison to stock HTML5 canvas handling on every machine I ran the game on.
Is there a KineticJS method for clearing the canvas in a manner such as clearRect()'s?
Edit:
I have also made sure it's not a problem on any other part of the program. Call stack doesn't overflow, the FPS drop is just due to DOM changing twice every .02 second.
Edit 2:
I have tried the following:
Ignore the layer before and create a blank rectangle to fill up the visible part of the canvas. It dropped my frame rate to about 14 FPS;
Use the .clear() method. It solved the DOM consistency problem but the frame rate got even lower than before.
It seems the only solution would be calling the default HTML5 clearRect() method, but that would mean creating the canvas element by hand (and possibly making Kinetic useless as a library for my app).
Edit 3:
As for the app, I've started using standard HTML5 canvas since I have a deadline. I'd still like to see a Kinetic solution though - it might be helpful in the future. It surprises me to see such a simple thing is so hard, if not impossible, in a popular library like KineticJS.
You can use layer.clear with a bounding area to clear just the "dirty" part of your layer.
// tell layer not to auto-clear on layer.draw
layer.setClearBeforeDraw(false);
// clear the "dirty" portion of the canvas
layer.clear(100, 100, 150, 150);
// adjust some animation values and
// just draw the element that has changed
myRect.draw();
You should try to create new Layer for example:
var newLayer = new Kinetic.Layer();
Or call this function:
Canvas.clear();
Kinetic makes it very easy to draw using layers, groups and shapes.
If your view is properly make of these items you can easily remove them and they will be removed from the stage.
Perhaps you need to rewrite you code to make it work better in kinetic.
if you think your code is properly written you can try (as a workaround) to create kinetic rectangle and fill it with whatever you want to simulate a clear.

Concurrent freehand drawing in fabric.js

I'm using fabric.js for a collaborative whiteboard project. I want freehand drawing paths to be drawn concurrently by several users. To do this I figured I could create a new PencilBrush for each remote user and simulate freehand drawing by calling the onMouseDown(), onMouseMove() and onMouseUp() methods of the PencilBrush class whenever the remote user performs those actions. I've created a jsfiddle that does something like that: http://jsfiddle.net/dkostro/7sx8N/21/
It draws two paths concurrently and the user can also draw on the canvas meanwhile. As you see one of the paths is never seen before onMouseUp() and freehand drawing it's not very pretty. I figured the problem was that each PencilBrush instance clears the upper canvas element before calling the _render function, thus only one of the paths can appear simultaneously.
Can you think of a way to make it work smoothly without having to mess up too much with fabric.js code? The one solution I thought of is having a separate canvas element for each PencilBrush, but that would be messing up with fabric.js code, and may not be a clean way to do it. Any other suggestions?
EDIT
I have implemented this one solution I have been thinking of in my question (separate canvas element for each user). http://jsfiddle.net/stropitek/DzygL/14/. I am not sure to what number of users this can scale to, or if this is a good solution, but in this example at least it seems to work well.

What's the fastest way to draw to an HTML 5 canvas?

I'm investigating the possibility of producing a game using only HTML's canvas as the display media. To take an example task I need to do, I need to construct the game environment from a number of isometric tiles. Of course, working in 2D means they by necessity come in rectangular packages so there's a large overlap between tiles.
I'm old enough that the natural solution to this problem is to call BitBltMasked. Oh wait, no, an HTML canvas doesn't have something as simple and as pleasing as BitBlt. It seems that the only way to dump pixel data in to a canvas is either with drawImage() which has no useful drawing modes that ignore the alpha channel or to use ImageData objects that have the image data in an array.. to which every. access. is. bounds. checked. and. therefore. dog. slow.
OK, that's more of a rant than a question (things the W3C like tend to provoke that from me), but what I really want to know is how to draw fast to a canvas? I'm finding it very difficult to ditch the feeling that doing 100s of drawImages() a second where every draw respects the alpha channel is inherently sinful and likely to make my application perform like arse in many browsers. On the other hand, the only way to implement BitBlt proper relies heavily on a browser using a hotspot-like execution technique to make it run fast.
Is there any way to draw fast across every possible implementation, or do I just have to forget about performance?
This is a really interesting problem, and there's a few interesting things you can do to solve it.
First, you should know that drawImage can accept a Canvas, not just an image. The "sub-Canvas"es don't even need to be in the DOM. This means that you can do some compositing on one canvas, then draw it to another. This opens a whole world of optimization opportunities, especially in the context of isometric tiles.
Let's say you have an area that's 50 tiles long by 50 tiles wide (I'll say meters for the sake of my own sanity). You might divide the area into 10x10m chunks. Each chunk is represented by its own Canvas. To draw the full scene, you'd simply draw each of the chunks' Canvas objects to the main canvas that's shown to the user. If only four chunks (a 20x20m area), you would only perform four drawImage operations.
Of course, each of those individual chunks will need to render its own Canvas. On game ticks where nothing happens in the chunk, you simply don't do anything: the Canvas will remain unchanged and will be drawn as you'd expect. When something does change, you can do one of a few things depending on your game:
If your tiles extend into the third dimension (i.e.: you have a Z-axis), you can draw each "layer" of the chunk into its own Canvas and only update the layers that need to be updated. For example, if each chunk contains ten layers of depth, you'd have ten Canvas objects. If something on layer 6 was updated, you would only need to re-paint layer 6's Canvas (probably one drawImage per square meter, which would be 100), then perform one drawImage operation per layer in the chunk (ten) to re-draw the chunk's Canvas. Decreasing or increasing the chunk size may increase or decrease performance depending on the number of update you make to the environment in your game. Further optimizations can be made to eliminate drawImage calls for obscured tiles and the like.
If you don't have a third dimension, you can simply perform one drawImage per square meter of a chunk. If two chunks are updated, that's only 200 drawImage calls per tick (plus one call per chunk visible on the screen). If your game involves very few updates, decreasing the chunk size will decrease the number of calls even further.
You can perform updates to the chunks in their own game loop. If you're using requestAnimationFrame (as you should be), you only need to paint the chunk Canvas objects to the screen. Independently, you can perform game logic in a setTimeout loop or the like. Then, each chunk could be updated in its own tick between frames without affecting performance. This can also be done in a web worker using getImageData and putImageData to send the rendered chunk back to the main thread whenever it needs to be updated, though making this work seamlessly will take a good deal of effort.
The other option that you have is to use a library like pixi.js to render the scene using WebGL. Even for 2D, it will increase performance by decreasing the amount of work that the CPU needs to do and shifting that over to the GPU. I'd highly recommend checking it out.
I know that GameJS has blit operations, and I certainly assume any other html5 game libraries do as well (gameQuery, LimeJS, etc etc). I don't know if these packages have addressed the specific array-bounds-checking concern that you had, but in practice their samples seem to work plenty fast on all platforms.
You should not make assumptions about what speedups make sense. For example, the GameJS developer reports that he was going to implement dirty rectangle tracking but it turned out that modern browsers do this automatically---link.
For this reason and others, I suggest to get something working before thinking about the speed. Also, make use of drawing libraries, as the authors have presumably spent some time optimizing performance.
I have no personal knowledge about this, but you can look into the appMobi "direct canvas" HTML element which is allegedly a much faster version of normal canvas, link. I'm confused about whether this works in all browsers or just webkit browsers or just appMobi's own special browser.
Again, you should not make assumptions about what speedups make sense without a very deep knowledge of web browser internal processes. That webpage about "direct canvas" mentions a bunch of things that slow down canvas-drawing: "Reflowing text, mapping hot spots, creating indexes for reference links, on and on." Alpha-blending and array-bounds-checking are not mentioned as prominent causes of slowness!
Unfortunately, there's no way around the alpha composition overhead. Clipping may be one solution, but I doubt there would be much, if any, performance gain. Not to mention how complicated such a route would be to implement on irregular shapes.
When you have to draw the entire display, you're going to have to deal with the performance hit. Although afterwards, you have a whole screen's worth of pre-calculated alpha imagery and you can draw this image data at an offset in one drawImage call. Then, you would only have to individually draw the new tiles that are scrolled into view.
But still, the browser is having to redraw each pixel at a different location in the canvas. Which is quite expensive. It would be nice if there was a method for just scrolling pixels, but no luck there either.
One idea that comes to mind is that you could implement multiple canvases, translating each individual canvas instead of redrawing the pixels. This would allow the browser to decide how to redraw those pixels, in a more native way, at least in theory anyway. Then you could render the newly visible tiles on a new, or used/cached, canvas element. Positioning it to match up with the last screen render.
But that's just my two blits... I mean bits... duh, I mean cents :]

Categories