Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

In the canvas element, are floating point numbers actually treated as floats by the lineTo and arc methods?

For example:

context.moveTo(20.4562, 80.8923);
context.lineTo(120.1123, 90.2134);
context.arc(24.5113, 36.7989, 20.123, 0, Math.PI*2); 

Does canvas really support float number coordinates, or it just converting the float numbers to integers when drawing line, arc, rect, etc.?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
271 views
Welcome To Ask or Share your Answers For Others

1 Answer

A bitmap does not support floating point values on its own. It can only deal with integer values. The 2D context of the canvas deals mostly with paths which is per-SE not connected to the bitmap.

Paths are arbitrary and exists only as vectors internally. It's when they are stroked or filled they are put through a rasterizing process, ie. converted to a bitmap representation.

Paths and their assigned points in the bitmap can themselves hold fraction values, but a bitmap can't. However, it is possible to represent a fractional value using interpolation (sub-pixeling) which gives the impression of having a larger available bitmap resolution, which in turn removes the jaggy look if values where cut down to just integers.

(And resolution is the main point of doing interpolation. A screen is about the equivalent of 72-96 DPI. If the screen had a higher resolution, 300+ DPI, interpolation would not be needed, as is in the case with printing. But we are there now and drawings would look jaggy if interpolation wasn't used as a compensation for lack of resolution).

Here is how it works

When a pixel lands on a integer bound value, it is a simple case of setting the pixel to that value.

int

When you deal with a floating point there is no where to set a pixel in part, it has to be either be rounded down (floor'ed) or placed in the next cell (ceil'ed), ie. 3.2 becomes 3, 3.7 becomes 4 etc.

intvsfloat

Interpolation / sub-pixeling

However, someone came up with the idea some decades ago to represent the fractional part as a blend between the actual pixel and the next pixel.

If the pixel value was 3.5, the fractional part would represent here 50% of black and 50% of white. It would still occupy a whole pixel cell, but since it is so tiny it will appear to just occupy a fraction of its cell thanks to the surrounding pixels which contribute to the illusion.

So in this case the last pixel set wold look like this:

subpixel 50%

In case the value was 3.25, only 25% of the remaining pixel would be mixed, making the last pixel look like:

subpixel 25%

This will apply to all pixels that would land on a fractional value. When you draw a diagonal line and some points "crosses" two pixels, a mix of those will be applied to the closed integer based placement, making the line look smooth.

Now, in case of canvas the shapes are interpolated with the alpha channel. Then the shape is blended and composited using Porter-Duff with the existing content, which is what we see as the final result.

This is also something to have in mind in regards to performance. If interpolation is needed the cost goes up, as for each pixel the browser (or sub-system) has to calculate the fractional representation. For this reason you could make sure to use integer values by rounding them off before passing them to the path method. This is of course not doable with arcs, ellipses, diagonal lines at some degrees etc., but it can help speed up things in some cases.

This is the principle with interpolation which applies when fractional coordinates are used. However, there are more complex ways of interpolate and sub-pixel. In modern world 2x2 or 4x4 sampling is more common to give a more accurate result.

Demo

If we make a small zoomed in line we can see the end of the line "fade" in and out when we variate the fraction value.

This line is 3 pixels wide, then we add a fraction value between [0, 1] to it and we can see the last ceil'ed pixel is being resampled:

var ctx = document.querySelector("canvas").getContext("2d"),
    x = 3.1, dx = 0.1;

ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.webkitImageSmoothingEnabled = false;
ctx.font = "14px sans-serif";

(function loop() {
  ctx.fillStyle = "#000";
  ctx.clearRect(0,0,350,50);
  ctx.fillRect(0,0, x, 1); // forces pixel-alignment for demo

  ctx.drawImage(ctx.canvas, 0,0,4,1, 0, 0, 200, 50);
  
  x += dx;
  if (x <= 3 || x >= 4) dx = -dx;

  info(x);
  document.querySelector("div").innerHTML = "length: " + x.toFixed(2);
  
  setTimeout(loop, 160)
})();

function info(x) {
  ctx.fillStyle = "#f00";

  ctx.fillText("Perceived length", 210, 19);
  ctx.fillRect(0, 15, x * 50, 2);
  ctx.fillRect(x*50-1, 12, 2, 7);

  x = x === 3 ? 3 : 4;
  ctx.fillText("Actual length", 210, 40);
  ctx.fillRect(0, 35, x * 50, 2);
  ctx.fillRect(x*50-1, 32, 2, 7);
}
div {font:bold 14px sans-serif}
<canvas width=500 height=50></canvas><br>
<div></div>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...