This wide- and large- screen layout may not work quite right without Javascript.

Maybe enable Javascript, then try again.

Chuck Kollars` Personal Home Fiddling with Home PCs

Canvas:
Automatic Scaling
Between Two Coordinate Systems

HTML's <canvas...> is both simple and powerful. It may seem to require rather complex programming just to do ho-hum things. But in fact it can do a whole lot easily, and it doesn't justify a nerds only sign. To unlock its full power, you should understand in some depth how it works.

Canvas Background

The <canvas...> element provides sophisticated capabilities by using two different coordinate systems simultaneously. (Unfortunately <canvas...> use often disregards —or even neuters— the available sophistication.)

The <canvas...> element is unlike almost all other HTML/HTML5 elements in using two different coordinate system scales simultaneously. The model coordinate system scale is used whenever you want to draw anything on the canvas. The display coordinate system scale is used to control how much physical screen space is dedicated to the canvas. You should explicitly specify both, the model coordinate size as attributes in your HTML, and the display coordinate size in your CSS.

Sometimes the two coordinate systems are identical, providing a 1:1 mapping with no scaling (and possibly a too-cheap sense of mastery). Most times the two coordinate systems are at different scales. (In fact, in some cases the X/horizontal scaling factor is not even the same as the Y/vertical scaling factor.) Whenever the model and display scales differ, the browser always auto-scales (and auto-re-scales) everything drawn on the canvas, so you never need be concerned with explicit scaling (not even for canvases that change size when the screen is rotated).

Having two different coordinate systems, one for drawing on the canvas and the other for positioning the canvas within the webpage, means you can change the webpage's appearance later without affecting the drawing instructions. Changing the size of the displayed canvas changes only the display coordinates; the model coordinates used by the Javascript drawing commands aren't affected. Thus you can change the displayed size of a canvas without having to make any changes at all to the associated Javascript. The canvas could even be displayed in different sizes on different devices, yet be driven by the same single simple Javascript code in all cases. (You should virtually never change the model scale of a canvas on the fly by changing its height/width attributes/properties.)

(top)

Example Showing The Two Coordinate Systems

Here's an illustration of the two coordinate systems. This canvas has model dimensions of 100x100 specified in the HTML. The crossed lines at 25,25 and 75,75 use the model coordinate system. But the actual physical display of the canvas (including what you see on this webpage) occupies a space of 225x225 pixels. (The display coordinate system refers to CSS/display pixels, which are usually the same as physical pixels, but may be different for some browsers with a few displays [especially very high resolution displays].) The grid behind the canvas shows a line every 50 pixels in the display coordinate system, and the labels just outside the corners of the canvas are in display coordinates.

(Canvas uses the same type of coordinate systems as all the rest of HTML/CSS, virtually all PC graphics, and almost all other computerized image processing: a cartesian grid with the origin [0,0] at the top left and the Y coordinate increasing in the downward direction. Such coordinate systems for computer graphics are natural, and quickly become intuitive; they require no additional specifications or calculations -not even prepending a minus sign.)

(Both canvas coordinates and CSS coordinates are zero-based, so for example the columns for a 100 pixel wide canvas are actually numbered from 0 through 99. For simplicity and focus on the concept of two simultaneous coordinate systems, the examples below just cheerfully ignore this fact. Please overlook as unimportant the many "off by one" errors that result.)

(These examples have intentionally been arranged to exaggerate the fuzziness problem. Typical canvases will look better. In fact, if you take just a bit of care, they will look a whole lot better. See the section on scaling below for details.)

two coordinate systems simultaneously

HTML:

<canvas id="cvs" width="100" height="100"><p>canvas unsupported</p></canvas>

CSS:

#cvs  { width: 225px; height: 225px; }

Javascript:

var cvs = document.getElementById('cvs');
var ctx = cvs.getContext('2d');
ctx.font = 'italic bold 8px serif';
ctx.lineWidth = 1;
ctx.fillStyle = '#aaaa00';

firstcross(ctx);
secondcross(ctx);

function firstcross(context) {
  context.moveTo(25,0);
  context.lineTo(25,100);
  context.stroke();

  context.moveTo(0,25);
  context.lineTo(100,25);
  context.stroke();

  context.beginPath();
  context.arc(25,25, 2, 0,Math.PI*2);
  context.closePath();
  context.fill();

  context.fillText("25,25", 1,22);
}

function secondcross(context) {
  context.moveTo(75,0);
  context.lineTo(75,100);
  context.stroke();

  context.moveTo(0,75);
  context.lineTo(100,75);
  context.stroke();

  context.beginPath();
  context.arc(75,75, 2, 0,Math.PI*2);
  context.closePath();
  context.fill();

  context.fillText("75,75", 76,83);
}

(top)

Specifying Both Coordinate Systems

The display coordinate system is specified with the usual width: ..._; and height: ..._; in CSS. The model coordinate system is specified with the attributes width="..." and height="..." inside the <canvas...> tag itself in the source HTML. (Specifying width="..." and height="..." in the HTML source itself looks odd [or even just plain wrong], as these days it's considered bad practice to do so for most HTML tags. But for the <canvas...> tag [unlike most HTML tags], it's the right thing to do.)

What happens if the coordinate systems are under-specified? If only the model coordinate system is specified (by attributes on the canvas tag itself in the HTML), those values will also be copied to the display coordinates in place of the missing values in the CSS. The result will be a 1:1 mapping with no scaling. If only the display coordinate system is specified (by using the CSS), the model coordinates may default to unexpected values, resulting in unintended scaling and mysterious drawing commands. If neither coordinate system is specified, the model coordinates will use default values, then those values will be copied to the display coordinates as well. Although this will result in a 1:1 mapping with no scaling, it will produce unexpected canvas dimensions.

(top)

Retaining Aspect Ratio

It's usually a good idea to have the canvas retain the same aspect ratio whether it's described in model coordinates or display coordinates. (In hopes of clearer communication though, some of the examples below do not retain the same aspect ratio.) You could do this yourself by handling all the math for both the model width and height and the display width and height. But it's easy to goof, especially if something is changed later. So better, you can make the browser always calculate the dimensions in a way that will retain the same aspect ratio in both coordinate systems.

Do this the same way you'd do it for an <img...>: in the CSS specify the scaling in only one direction (say for example X/horizontal), and specify the other dimension as auto. For example to make the display coordinates double what the model coordinates are, yet always automatically retain the same aspect ratio, even if the height is changed, you could code like this:

HTML:

<canvas id="cvs" width="37" height="56"><p>canvas unsupported</p></canvas>

CSS:

#cvs  { width: 74; height: auto; }


Here's another example, where the size of the canvas is controlled by the rest of the layout, yet still the canvas aspect ratio is retained. Here even though the size of the canvas varies with the size of its parent element in the layout, the aspect ratio of the displayed canvas doesn't change.

HTML:

<canvas id="cvs" width="100" height="150"><p>canvas unsupported</p></canvas>

CSS:

#cvs  { width: 65%; height: auto; }

(top)

Drawing On A Canvas

An HTML canvas is drawn on with Javascript commands. There are provisions for drawing geometric figures (colors and drop shadows on points, lines, and simple shapes), and for copying images. (It's also possible to use Javascript to go through a canvas one pixel at a time and modify each one. But doing so isn't what this page is about.) There are provisions for drawing simple text labels. Canvas handling of text is pretty basic and probably only for labels though; it doesn't extend easily to multi-line text, and building a text-editing widget with word wrap would be very difficult. The coordinate shift functions available elsewhere in CSS3 (translate, rotate, scale) are also available, but are unnecessary for geometric figures.

Most Javascript references and operations (mainly drawing of course) use only the model coordinate system. (Well almost: there's one significant exception where Javascript may need to be aware of the display coordinate system when dealing with a canvas ...see below.) The canvas size in model coordinates is always easily available from the DOM via element.width and element.height. There are just a few places where Javascript can access the display coordinate system: CSS-related variables such as element.style.xxx and the structured values returned by document.getComputedStyle(...), and layout variables such as the various element.offsetXxxx.

To illustrate the process of drawing on a canvas, we start with the exact same example canvas as above, then additionally draw a colored rectangle on it.

drawing vs. display

HTML:

<!-- (same) -->
<canvas id="cvs" width="100" height="100"><p>canvas unsupported</p></canvas>

CSS:

/* (same) */
#cvs  { width: 225px; height: 225px; }

Javascript:

var cvs = document.getElementById('cvs');
var ctx = cvs.getContext('2d');
ctx.font = 'italic bold 8px serif';
ctx.lineWidth = 1;
ctx.fillStyle = '#aaaa00';

firstcross(ctx);
secondcross(ctx);
// In Addition...
ctx.fillRect(25,25, 50,50);

// First agument pair is coordinates of upper left corner of rectangle,
// second argument pair is size of rectangle in pixels

function firstcross(context) {
  context.moveTo(25,0);
  context.lineTo(25,100);
  context.stroke();

  context.moveTo(0,25);
  context.lineTo(100,25);
  context.stroke();

  context.beginPath();
  context.arc(25,25, 1.5, 0,Math.PI*2);
  context.closePath();
  context.fill();

  context.fillText("25,25", 1,22);
}

function secondcross(context) {
  context.moveTo(75,0);
  context.lineTo(75,100);
  context.stroke();

  context.moveTo(0,75);
  context.lineTo(100,75);
  context.stroke();

  context.beginPath();
  context.arc(75,75,2, 0,Math.PI*2);
  context.closePath();
  context.fill();

  context.fillText("75,75", 76,83);
}

(top)

The Exception: Display Coordinates Intrude Javascript

All events always use the display coordinate system. This includes even a mouse click on a canvas. event.screenX and event.screenY will be in the display coordinate system (not the model coordinate system). Even if your application finds it more convenient to use event.clientX and event.clientY, these too will still be in the display coordinate scale.

While all other scaling is performed automatically and internally by the browser, in this case you may need to do some scale conversion explicitly yourself. Many recipes for converting mouse click coordinates (display scale) to canvas coordinates (model scale) are easily available. (Unfortunately few of the available methods describe clearly why such a conversion is necessary in the first place.) Here's one very simple way to convert coordinates from display scale to model scale:

// scale from display coordinates to model coordinates
var modelX = Math.round( event.screenX * (canvas.width / canvas.offsetWidth) );
var modelY = Math.round( event.screenY * (canvas.height / canvas.offsetHeight) );


To do the scale conversion in the other direction, just flip the ratio, like this:

// scale from model coordinates to display coordinates
var displayX = Math.round( modelX * (canvas.offsetWidth / canvas.width) );
var displayY = Math.round( modelY * (canvas.offsetHeight / canvas.height) );

(top)

Translating/Scaling

Conversion of a canvas from the model coordinates used when drawing on it to the display coordinates used when painting it on the screen is handled entirely by the browser. The programmer has no part in (in fact not even any input to) this process. Once you define the width and height of both coordinate systems, the rest is completely out of your hands.

Conversion is done on the fly, for visible elements and also apparently for currently invisible elements. Also, it is redone on the fly whenever necessary (for example if the CSS size of the canvas changes when the device orientation is changed). Conversion applies both to things already drawn on the canvas and to new drawings. (You may see this behavior called auto-scaling or auto re-scaling).

The exact conversion algorithms used vary between browsers. Typically they are the common pixel-by-pixel algorithms with an emphasis on speed used by most image manipulation tools. They work very well for most pictures, but often don't work quite so well for blowing up simple drawings such as charts that have hard straight edges. Just as with any image manipulation, shrinking typically looks better than enlarging, and scaling can make some images look jaggy or fuzzy.

Keep image scaling in mind when defining your coordinate systems and testing your canvas. Make your model coordinate system large, perhaps larger than your largest display coordinate system. Specify all points and sizes as even numbers (2, 4, 6, ...) to most easily avoid half-a-pixel oddities. (Especially watch out for ctx.lineWidth=..., as it defaults to the odd number 1, so the default results do not always scale as well as they could.) And if your results must always appear as sharp as possible with all browsers, go even further and arrange it so the scaling factors between your model coordinate system and your display coordinate systems are exact powers/fractions of two (including power zero for 1:1 scaling).

Here are the two examples from above again, almost exactly the same as before. The only difference is the vertical dimension is scaled differently by defining a different CSS height.

auto-scaling

HTML:

<!-- same -->

CSS:

#cvs  { width: 225px; height: 125px; }

Javascript:

// same



relationship between drawing and auto-scaling

HTML:

<!-- same -->

CSS:

#cvs  { width: 225px; height: 125px; }

Javascript:

// same

(top)


Location: (N) 42.680943, (W) -70.839384
 (North America> USA> Massachusetts> Boston> Metro North> Ipswich)

Email comments to Chuck Kollars
Time: UTC-5 (USA Eastern Time Zone)
 (UTC-4 summertime --"daylight savings time")

Peruse Chuck Kollars' Facebook Profile
All content on this Personal Website (including text, photographs, audio files, and any other original works), unless otherwise noted on individual webpages, are available to anyone for re-use (reproduction, modification, derivation, distribution, etc.) for any non-commercial purpose under a Creative Commons License.