Save <canvas> contents to be redrawn in later animation frames? - javascript

I am drawing a graph on a <canvas> that requires expensive calculations. I would like to create an animation (when moving the mouse across the canvas) where the graph is unchanging, but some other objects are drawn over it.
Because the canvas will have to be redrawn a lot, I don't want to perform the calculations to render the graph for every frame. How can I draw the graph once, save it, and then use the saved rendering to redraw subsequent frames of the animation, so that the expensive calculations only have to happen once & all I have to redraw is the much simpler animation layer?
I tried drawing the graph on a second canvas & then using ctx.drawImage() to render it onto the main canvas, but drawing on the canvas doesn't seem to work unless it's in the dom & not display:none;. Do I have to do something hacky like position the temp canvas out of view, or is there a cleaner way to do this?

You need to use at least 2 canvases : one with the complex drawing, and the second, on top of the first (with the same size, positioned in absolute), with the animated shapes. This method will work on IE, and getImageData doesn't work with ExCanvas.
Every library which does complex drawings on canvases use this method (Flot and others).
<div style="width: 600px; height: 300px; position: relative;" id="container">
<canvas class="canvas" style="position: absolute; left: 0px; top: 0px;" width="600" height="300"/>
<canvas class="overlay" style="position: absolute; left: 0px; top: 0px;" width="600" height="300"/>
</div>

How about drawing your graph the first time on your canvas and then
var imdata = ctx.getImageData(0,0,width,height);
and then
ctx.putImageData( imdata, 0,0);
for the rest of the rendering.

I had to make a few changes to the flot.js charting library. I'm 99% sure that it uses overlapping canvases. There's a chart layer and an overlay layer. You could look at the source code.

Related

Display zoomed-out canvas on screen but download full-size canvas with `toDataURL`

In my web application, I have a canvas with a download button that will download the current canvas as a PNG using .toDataURL.
Let's say my full-size canvas is 100px by 100px
I would like to display the canvas on the web page at a scaled-down scale (let's say 50%) but I would like the download to download the full-scale image. I am stuck on how to achieve this. Right now I am scaling down the whole canvas (both the width and height of the canvas element as well as using fabricCanvas's setZoom function to scale the image for display, but this means that toDataURL generates a zoomed-out image for download.
Ideally I would like the canvas to internally be rendered at full size (so toDataURL will generate a full-size png) but displayed on screen at a scaled-down size.
TL;DR: Is there a way to display a 50x50 px canvas on screen, yet download a 100x100 version using toDataURL?
Fiddle:
https://jsfiddle.net/pahxjd1s/35
One thing is the size of the <canvas> element and another the size of its bitmap.
If you want the real size of the image to be 100x100 just set the size with width and height attributes.
Now to have the <canvas> element with a size of 50x50 just use CSS.
<style>
.canvas {
width: 50px;
height: 50px;
}
</style>
<canvas class="canvas" width="100" height="100"></canvas>
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#sizing_the_canvas_using_css_versus_html
Not sure if this would work for you, but a quick way would be to apply a scale transform to the parent div.
<div style={{ display: "inline-block", border: "solid", transform: `scale(${SCALE})`}}>
<canvas
ref={ref}
width={100}
height={100}
/>
</div>
This will also resize the control points of fabricjs and add a margin (because fabric manages the style of canvas directly). you can counter the margin by adding a margin: `-${100 * SCALE / 2}px`
I think the ideal solution would likely have a secondary canvas for the higher resolution, or generate one on demand, but this trick might be enough to get you your result.

HTML5 canvas dirty rectangle Buggy Issue

I am using dirty rectangles to animate a tank go around a map. For fair game-play, the tank's torso can travel only in the cardinal directions and it's top cannon (separate from the torso, but attached to it) can also rotate in the cardinal directions.
The issue is that when I use clearRect, it may clear other objects, and obstacles immediate to it. clearRect is called as the tank moves in the certain velocity. Is it possible (without any premade physicsEngine / extermal Gitub API I have to lean), to animate the tank such that, it doesn't clear objects around it?
I've tried multiple canvases too..
How would I overlay multiple canvases, so the animation still shows, but the clearRect in one doesn't affect any object in the other?
Image of sample issue:
There are two (common) options:
Draw the tanks only on a separate canvas so when you clear a tank only that canvas is affected. You can overlay canvases using fixed or absolute positioned elements.
Use a single canvas but redraw everything. You can make the redraw more efficient by using a clipping mask for the region you want to redraw.
1) To overlay canvases:
<div id="canvasContainer">
<canvas id="canvas1" ... ></canvas>
<canvas id="canvas2" ... ></canvas>
</div>
CSS:
#canvasContainer {
position:relative;
}
#canvasContainer > canvas {
position:absolute;
left:0;
top:0;
}
2) To set a clipping mask:
ctx.clearRect(x, y, w, h);
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.save();
ctx.clip();
/// redraw everything, only clipped region will be updated
ctx.restore();
Of course, if your code/setup allow you can just redraw the area inside the regions you cleared instead of everything but if this is possible depends entirely on the draw logic you have implemented.

how to copy another canvas's data on the canvas with getContext('webgl')?

I have a canvas for showing medical image and I have another canvas for annotating images by draw shape or line.
when I draw a line on canvas#2 I want to copy drawn line on canvas#1 something like this:
var context = canvas1.getContext('2d');
context.drawImage(canvas2,0,0);
but because my canvas#1 has a getContext('webgl') I can't do that.
I mean how to simulate
drawImage() for getcontext('webgl')?
I really appreciate any help or advice.
Regards;
Zohreh
Well, you could just use the toDataURL method of the webgl canvas to convert it into an image. Then draw it on the 2d context.
ctx2D.drawImage(webGLCanvas.toDataURL("image/png"), 0, 0);
In this case I believe you might have to set the preserveDrawingBuffer propertie of the webgl canvas to true when initializing it.
...getContext("webgl", {preserveDrawingBuffer: true});
I had a similar problem with Emscripten, needed to copy WebGL canvas to another empty canvas.
I used this code but got empty screen.
var sourceCanvasWebGL = document.getElementById( "canvas" );
var destinationCanvas = document.getElementById( "canvasCopy" );
var destinationCanvasContext = destinationCanvas.getContext('2d');
destinationCanvasContext.drawImage(sourceCanvasWebGL, 0, 0);
After some googling (Saving canvas to image via canvas.toDataURL results in black rectangle) I figured out that because WebGL is using 2 buffers while drawing I am copying the old buffer with empty content.
Problem was solved by setting preserveDrawingBuffer: true in the code used to make WebGL drawing.
sourceCanvasWebGL.getContext("webgl2", { preserveDrawingBuffer: true })
P.S. As an alternative you could make an image copy right after rendering the canvas. If so you do not need preserveDrawingBuffer.
You could use a 2D canvas as the on-screen canvas, and draw the WebGL canvas to it when updating the WebGL canvas before drawing whatever annotations on.
drawWebGLStuff(webGLCtx);
// Copy image data of WebGL canvas to 2D canvas.
// This should be done right after WebGL rendering,
// unless preserveDrawingBuffer: true is passed during WebGL context creation,
// since WebGL will swap buffers by default, leaving no image in the drawing buffer,
// which is what we want to copy.
ctx2D.drawImage(webGLCanvas, 0, 0);
drawAnnotations(ctx2D);
(The annotations could be rendered each frame from a list of shapes or drawn to another offscreen canvas, which is then drawn to the main (on-screen) canvas similar to the WebGL canvas.)
Or you can simply layer the canvases on the page with absolute positioning and z-index:
<div style="position: relative;">
<canvas id="layer1" width="100" height="100"
style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
<canvas id="layer2" width="100" height="100"
style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
</div>

moving a turtle on a canvas

I have an assignment with which i have multiple problems.
Simply put we need to move a turtle on a canvas forwards/backwards rotating it right/left with a given amount.
The turtle can leave a "trace" (a colored line or something) behind him when moving and when he reaches the end of the canvas it has to appear on the other side
now one of my problems is how can i move the turtle without redrawing the whole canvas, because i can't lose the turtles "trace" when he moves
the other thing is that i have no problem moving it in the 4 "normal" directions and appearing and the other side but i think we need to be able to rotate it any amount of degrees and move it in that direction and i don't really know how to do this
my code is not very advanced for these reasons i only have the canvas and the form from which i get my parameters
i don't really know what else to say about it, if necessary i will include all the code i have
UPDATE:
i think for the first problem about redrawing the canvas i can use clearRect(locX,locY,imgWidth,imgHeight) so i clear the area where the image was and draw only the image next to it, but if i'm wrong i'm open for tips
You can use 2 overlapping canvases to more easily display your turtle and your tracks.
Put your tracks on the bottom canvas (no need to ever clear/erase this bottom canvas).
Put your turtle on the top canvas (always clear this canvas and redraw the turtle in its new position).
Here's the HTML:
<div id="wrapper">
<canvas id="canvasBottom" width=300 height=200></canvas>
<canvas id="canvasTop" width=300 height=200></canvas>
</div>
Here's the CSS used to overlap the 2 canvases:
#wrapper{
position:relative;
width:300px;
height:200px;
}
#canvasTop,#canvasBottom{
position:absolute; top:0px; left:0px;
border:1px solid green;
width:100%;
height:100%;
}
#canvasTop{
border:1px solid red;
}
To rotate your turtle, check out this tutorial on how to combine context.translate and context.rotate to display a rotated object:
http://www.html5canvastutorials.com/advanced/html5-canvas-transform-rotate-tutorial/
To move your turtle along a linear path, start out with this tutorial on how to animate an object:
http://www.html5canvastutorials.com/advanced/html5-canvas-linear-motion-animation/

How to manage memory in case of multiple fabric js canvas?

In my application, I have multiple Fabric.js canvases, There is no limit on the number of canvases. I'll render heavy JSON via loadFromJson method of Fabric.js.
So I want to release the fabric object memory if the canvas is not in use. How can I do that?
At a time only one canvas will be visible. But I have to render all the canvases as the page loads. Canvas is actually a page and user can switch between pages via clicking on page number or something else.
Remember user can come back to any canvas any time and try to doodle or use any other Fabric.js functionality.
Here is my HTML structure:
<style>
.fabricCanvas {
border: 1px solid green;
margin: 5px;
}
.canvas-container {
margin: 5px;
}
</style>
<canvas class="fabricCanvas" width="300" height="300"></canvas>
<canvas class="fabricCanvas" width="300" height="300"></canvas>
<canvas class="fabricCanvas" width="300" height="300"></canvas>
<canvas class="fabricCanvas" width="300" height="300"></canvas>
My JS code to store fabric instances
var canvasInstances = [];
$('canvas.fabricCanvas').each(function () {
var fabricCanvasObj = new fabric.Canvas(this, {
isDrawingMode: true
});
canvasInstances.push(fabricCanvasObj);
fabricCanvasObj.renderAll();
});
console.log(canvasInstances[0]);
I am storing instances so that I can use them later. I want this for better memory management, basically loading and unloading instances as and when needed.
Sample situation DEMO is here. In this demo consider that the canvases are over each other using z-indexes but they are the part of DOM and has already been rendered on page load.
Let me know in case of any doubt, I can explain further.
When ever there are more than 5 canvases iPad browser crashes which I think is the memory issue.
You might be interested in 3 things (in the order of significance/destruction):
canvas.clear() — removes all canvas objects from it.
canvas.dispose() — removes all canvas objects AND removes all event listeners
$(canvas.wrapperEl).remove() (using jQuery for illustrative purposes) — to remove canvas wrapper element (which contains upper and lower canvases used by Fabric). This can be done AFTER you call dispose, if the goal is to completely remove Fabric canvas from a document.

Categories