Advise please: Small pen on a large canvas - user-interface

I'm currently drawing a line chart on a html5 canvas (In plan vanilla JavaScript) with a width of 1px and moving along as I draw on the x axis 2px spacing per data point. Currently my canvas size is 1000px by 300px.
My data is most of the time much larger than my canvas. I need some idea of a smart approach to zooming (or to make it seem like you are zooming) as I would like to be able to zoom and drag the view-able area around without loosing the crispness of the 1px line.
A note: The canvas could be a drawing of cat for all it matters, for the sake of a clear question if it was a cat then the cat would be much larger than the canvas and you might, as a user, be interested at looking closely at its foot and scrolling around or zooming out to see the whole cat. The real problem I see is the fact that it is a line drawing of 1px thickness.
Would it be more practical to change (increase/decrease) the x spacing and the magnitude of the the y movements when drawing? So that this way If zoomed out far, I would be drawing still with 1px thickness and still drawing on the same size canvas but moving much more fin-eight distances. This way I would have to repaint I think every time I navigate the area and if altering zoom. Also the canvas would not need to be zoomed with css.
Or would It be better to Increase the size of the canvas to a much much larger one and change the thickness of the line with each zoom? So this way when you are zoomed out the line thickness would be greater than if you were zoomed in but the distance and spacing between movements would always be the same no matter what level the zoom was. Also this way I assume I would have to repaint only when zooming the canvas element with css to change line width whereas scrolling the drawing would be fine as the whole drawing would all-ways fit into the large canvas.
I have heard that there are limitations on size and rendering on different browsers for a start and I would like to know If anyone has had any experience in dealing with large canvas drawings.
For further detail: My data points are around 70,000 long I will be increasing to 100,000 data points so the canvas would be quite big, hence my concern (it is a static chart so no worries about stalling the browser with such a large task).
What would be the most 'do-able' way and would there be a more logical approach to this task?
Please no library's.

You can keep your crisp fine lines when zooming by setting the canvas CSS size much smaller than the canvas element size.
Example:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvas1=document.getElementById("canvas1");
var ctx1=canvas1.getContext("2d");
// draw on standard canvas
ctx1.beginPath();
ctx1.moveTo(50/4,50/4);
ctx1.lineTo(250/4,250/4);
ctx1.stroke();
// draw on resolution enhanced canvas
ctx.lineWidth=4;
ctx.beginPath();
ctx.moveTo(50,50);
ctx.lineTo(250,250);
ctx.stroke();
#canvas{border:1px solid red; width:100px; height:100px;}
#canvas1{border:1px solid blue; width:100px; height:100px;}
<h4>Left: Standard canvas, Right: Resolution enhanced</h4>
<h4>Zoom your browser to notice the difference (eg 200%)</h4>
<canvas id="canvas1" width=100 height=100></canvas>
<canvas id="canvas" width=400 height=400></canvas>

Related

External elements slowing down canvas

I am developing a game using several canvases (3) on top of one another. I am close to finishing the game and I haven't yet optimized the performance.
Regardless, my main concern is that the game has performed pretty well so far, but being close to finish I am building a simple web page around the canvas to give a frame to the game. I am talking just putting the title of the game and a few links here and there, but suddenly the game is now choppy and slow!!! If remove those elements everything is smooth again.
The culprits are:
The game title above the canvas (styled with text-shadow).
four buttons below the canvas to redirect to other sites and credits.
Is it possible that this few static elements interfere with the rendering of the game?
Thank you.
Anything with shadows, rounded corners or expensive effects such as blur cost a lot to render.
Modern browsers try to optimize this in various way but there are special cases which they can't get around just like that (updated render engines using 3D hardware can help in the future).
Shadows are closely related to blurring and needs to be composited per frame due to the possibility that the background, shadow color, blur range etc. could change. Rounded corners forces the browser to create an alpha mask instead of doing just a rectangular clip. The browser may cache some of these operations, but they'll add up in the end.
Text Shadow
A workaround is to "cache" the shadowed text as an image. It can be a pre-made image from Photoshop or it could be made dynamically using a canvas element. Then display this instead of the text+shadow.
Example
var ctx = c.getContext("2d"),
txt = "SHADOW HEADER";
// we need to do this twice as when we set width of canvas, state is cleared
ctx.font = "bold 28px sans-serif";
c.width = ctx.measureText(txt).width + 20; // add space for shadow
c.height = 50; // estimated
// and again...
ctx.font = "bold 28px sans-serif";
ctx.textBaseline = "top";
ctx.textAlign = "left";
ctx.shadowBlur = 9;
ctx.shadowOffsetX = 9;
ctx.shadowOffsetY = 9;
ctx.shadowColor = "rgba(0,0,0,0.8)";
ctx.fillStyle = "#aaa";
ctx.fillText(txt, 0, 0);
body {background:#7C3939}
<canvas id=c></canvas>
The canvas element can now be placed as needed. In addition you could convert the canvas to an image and use that without the extra overhead.
Rounded Corners
Rounded corners on an element is also expensive and there are no easy way around this - the corners need to be cut one way or another and question is which method is fastest.
Let browser do it using CSS
Overlay the element with the outer corners covered in the same color as background - clunky but can be fast as no clipping is needed. However, more data need to be composited.
Use a mask in canvas directly via globalCompositeOperation. The chances are this would be the slowest method. Performance tests must be made for this scenario to find out which one works best overall.
Make a compromise and remove rounded corners all together.
Links
Also these could be replaced by clickable images. It's a bit more tedious but also these could be made dynamically using a canvas allowing the text to change ad-hoc.
CSS
I would also recommend experimenting with position: fixed; for some of the elements. When fixed is used, some browsers renders that element separately (gives it its own bitmap). This may be more efficient in some cases.
But do make some performance tests to see what combination is the best for your scenario.

Why does canvas re-render the entire canvas rather than only a specific portion?

From my very limited html5 canvas experience, it seems like the way to animate something is to clear the entire canvas, and draw everything from scratch.
This doesn't seem very performant. I'm wondering why this was the chosen approach?
Is there an alternative? For example, using an object-oriented approach, if you wanted to re-render a tree in the foreground, the system should cache the background, and only rerender that layer.
Your understanding is correct.
Typical canvas apps will completely erase the canvas and redraw objects.
This process works well because Html Canvas is designed with blazingly fast drawing speed.
Unlike object oriented design, the data that draws on the canvas has been completely "flattened".
There is a single data array containing the Red, Green, Blue & Alpha components of all pixels on the canvas.
[
pixel1Red, pixel1Green, pixel1Blue, pixel1Alpha,
pixel2Red, pixel2Green, pixel2Blue, pixel2Alpha,
pixel3Red, pixel3Green, pixel3Blue, pixel3Alpha,
...
]
This means that any color component of any pixel can be accessed with a single jump.
This flat structure also means that if an image needs to be drawn on the canvas, the browser only needs to copy sequential data from the source image directly into sequential data in the canvas pixel array.
In addition, Canvas is hardware accelerated when a GPU is available.
That's the basic technique, yes. You could also clear a specific area of the canvas instead, using clearRect():
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Drawing_shapes
// Clear the specified rectangular area from x, y through width, height.
context.clearRect(x, y, width, height);
In your case of change foregrounds and backgrounds, however, consider modifying the globalCompositeOperation, which enables you to draw shapes under existing shapes:
// Draw new shapes behind the existing canvas content.
ctx.globalCompositeOperation = 'destination-over';
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Compositing
Another useful method is clip(), which allows you to draw shapes as masks:
// Create a circular clipping path.
ctx.beginPath();
ctx.arc(0, 0, 60, 0, Math.PI * 2, true);
ctx.clip();
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Compositing

HTML5 canvas - Cannot clear dirty pixels - Retina related

I have a simple canvas with a mouseover event. As the user moves the mouse, I want to draw a single pixel at the event's x,y coords (in the future it will be more complex than a single pixel). Essentially it's like a custom cursor.
The logic is extremely simple as shown below. Strangely, although I'm cleaning up the old pixel location, there're tiny remnants of the pixel left behind (since I posted this question, I have discovered it's related to the Retina display). I've been able to work around this. Instead of saving 1px and restore 1px, I save 3px and restore 3px. But I don't understand why I need to do this, and in the future when I'm drawing a more complicated cursor, I want the dirty pixel handling to be precise.
Here's a runnable JSFiddle example: http://jsfiddle.net/sbCq3/2/
// cleanup previously drawn pixel
ctx.putImageData(lastImageData, lastImageX, lastImageY);
// save the imageData currently at x,y
lastImageData = ctx.getImageData(x, y, 1, 1);
lastImageX = x;
lastImageY = y;
// draw the dot
var dotData = ctx.createImageData(1, 1);
...
ctx.putImageData(dotData, x, y);
I'm a bit stumped. I'm wondering if it has anything to do with my Retina display. If I draw a single pixel at 5,5, it's a single pixel in the image data - but I can zoom using the DigitalColor Meter (built in zoom tool) and see that single pixel is sub-divided and anti-aliased. Whereas if I view that pixel on a Windows machine it's a nice solid pixel. I haven't tested to see if this problem appears on Windows or non-retina machines yet. (I'm not referring to the normal canvas anti-aliasing problem).
UPDATE: I just tested this on my coworkers non-retina MacBook 17" and it works perfectly fine. So this definitely appears to be related to the Retina display.
I'm not entirely sure, but I think this may have to do with the resolutions of the different screens. If you've ever looked at an html5 canvas on a smartphone, which most have a much greater resolution per sq inch than a monitor, then you'll see that the quality is much degraded because the web browser expands the 100px that you tell it to use to the same physical size, which ends up being around ~130px. I think the same is happening with your retina display because it uses a crazy good resolution. Basically, different screens have different pixel size ratios. To get around this, I added the following to my code.
var PIXEL_RATIO = (function() {
var ctx = document.createElement("canvas").getContext("2d"),
dpr = window.devicePixelRatio || 1,
bsr = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backignStorePixelRatio || 1;
return dpr / bsr;
})();
Now, whenever you make a canvas or draw anything, multiply your constants by the variable PIXEL_RATIO.

Modify shapes using javascript in HTML Canvas

I have started learning CANVAS. After i started drawing some basic shapes, i wanted to make some modifications to them. For example, I am confused of how to modify length and width of rectangle. Should i have to clear the canvas and redraw or can i capture the object of that rectangle like the objects in java script.
The canvas is a raster graphics surface. modifying length and width of a rectangle is a vector action. It is possible to scale a raster, but losses in quality can/will occur. You can use vector graphics in the form of SVG. But if it is only a rectangle, use a div with a border overlay-ed on your canvas.

Why HTML5 Canvas with a larger size stretch a drawn line?

I am using a HTML5 <canvas> to draw lines on.
When I change the dimensions from the default of 300x150 to match the dimensions of another <div> my lines appear stretched and are thicker than before. Why is this happening?
I want to draw the lines on the canvas to match the position of elements on the <div> that is in front of it (with a higher z-index) in the same position on the page. How can I stop this stretching from happening?
EDIT: This appears only to be in Firefox.
You can't change canvas size with CSS. If you do it will behave like an image and it will stretch You have to do it in javascript with canvas properties canvas.width, canvas.height

Resources