Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Canvas 2D tutorial needs to discuss how shapes actually work and fix issues #38156

Open
greggman opened this issue Feb 14, 2025 · 3 comments
Open
Labels
Content:WebAPI Web API docs help wanted If you know something about this topic, we would love your help!

Comments

@greggman
Copy link
Contributor

greggman commented Feb 14, 2025

MDN URL

https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes

What specific section or headline is this issue about?

The Grid and Drawing Rectangles

What information was incorrect, unhelpful, or incomplete?

The article does not explain how the canvas API really works. Further, it has some false information, possibly because that explanation is missing.

For example: The article shows this code

    ctx.fillRect(25, 25, 100, 100);
    ctx.clearRect(45, 45, 60, 60);
    ctx.strokeRect(50, 50, 50, 50);

It then explains it like this

The fillRect() function draws a large black square 100 pixels on each side. The clearRect() function then erases a 60x60 pixel square from the center, and then strokeRect() is called to create a rectangular outline 50x50 pixels within the cleared square.

This last part

strokeRect() is called to create a rectangular outline 50x50 pixels within the cleared square.

Is false.

What really happens is, ctx.strokeRect(50, 50, 50, 50); makes a rectangle from 4 lines from unit 50,50 that is 50 units wide and 50 units tall. It then expands each line 0.5 units in both direction perpendicular to the line (0.5 because the default lineWidth is 1.0). This ends up with a frame that is 1 unit thick that stats at 49.5,49.5 and is 51x51 units wide the buttom right is at 50.5, 50.5. In then fills in each pixel inside the frame in proportion to how much of that pixel is inside the frame. That's why the frame in the example is actually 2 pixels wide and is gray instead of black.

The article would benefit from first explaning that the canvas 2D API doesn't use pixels as units.

Maybe diagrams like this would help

First off. given code like this

ctx.fillStyle = 'red';
ctx.fillRect(2, 2, 10, 6);

I suspect many people, when they first encounter the API, assume the units are pixels like this

Image

Draw from pixel 2,2 10 pixels wide, 6 pixels high.

They are not pixels though, they are grid units specifying a rectangle like this

Image

Draw from 2,2 10 units wide, 6 units high

The pixels inside that rectangle are filled with 'red' (the fillStyle color) based on how much of each pixel the rectangle touches is inside the rectangle. In the case above, every pixel the rectangle touches is 100% inside the rectangle and so you the result most people expect

Image

But, move the rectangle over half a pixel

ctx.fillStyle = 'red';
ctx.fillRect(2.5, 2.5, 10, 6);

Image

Suddenly some pixels are only half covered by the rectangle. The corner pixels are only 25% inside the rectangle so you get this (*)

Image

You can see the pixels on the edge which were only 50% covered by the rectangle are only 50% red. The outer corners which are only 25% inside and so are 25% red. (*)

The next thing to understand is how strokeRect (and stroke) work.

Starting with the original rectangle with this code

ctx.strokeStyle = 'red';
ctx.strokefRect(2, 2, 10, 6);

First, again, that code defines this rectangle

Image

That outline gets expanded to be lineWidth thick, purpendicular to each line (the 4 lines that make the rectangle). Like this

Image

Then, just like before, the pixels the resulting shape touches are colored in proportion to how much they are inside the shape. The result is this

Image

You can see the shape covered the inner and outer edge pixels by 50% so they are 50% red. The outer corners are only covered 25% so they get 25% red. The inner corners are covered 75% so they get 75% red. (*)

This is why the current document saying

strokeRect() is called to create a rectangular outline 50x50 pixels within the cleared square.

Is wrong. That is not what happens.

It would be good to make this clear I think. It would also be good to point out that things like translate/rotate/scale effect these shapes and therefore affect how much of each pixel is covered.

Note: I'm not sure even "grid coordnates" is the best term. You just specifying "coordinates". those coordinate are transformed by the current transform. Then, whatever pixels are touched by the shape your drawing are colored in proportion to how much of the pixel is inside the shape.

(*) Note: AFAIK the spec is not clear on exactly how things are drawn. The results shown in the diagrams match Firefox Nightly 137 on an M1 Mac. Chrome and Safari each do things differently. For example in the stroke example, Chrome renders the same pixels but all of them are 50% red including the inner and outer corners.

And of course, further, the lineCap, lineWidth, and miterLimit settings affect how the lines are expanded into a shape before the shape is used to color pixels based on how much the shape covers them.

What did you expect to see?

Correct information and a clearer or more precise explanation

Do you have any supporting links, references, or citations?

https://html.spec.whatwg.org/multipage/canvas.html#2dcontext

https://jsgist.org/?src=f851375146e7b56b3f8495c2d3c8a80d

Do you have anything more you want to share?

No response

MDN metadata

Page report details
@greggman greggman added the needs triage Triage needed by staff and/or partners. Automatically applied when an issue is opened. label Feb 14, 2025
@github-actions github-actions bot added the Content:WebAPI Web API docs label Feb 14, 2025
@Josh-Cena
Copy link
Member

This is an introductory tutorial aiming for a high-level overview of how to draw shapes. It doesn't concern with the low-level detail of how pixels are aligned. Though that is certainly a critical information for understanding what exactly gets shown, it only obscures our conceptual understanding of these APIs. My personal take is we should introduce this technicality as late as https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas, but maybe others have different thoughts.

@greggman
Copy link
Contributor Author

greggman commented Feb 14, 2025

I get your point. Maybe there is a better way to introduce it. My point is having answered S.O. questions etc for the last 12+ years, so many people mis-understand why they get blurry shapes. They resort to voodoo (add +0.5) and have strange ideas like "ctx.scale makes things blurry".

No, that is not why it's blurry. It's blurry because the rectangle/shape you asked for, either the explicit one from fillRect, or the implicit one from lines really being small rectangles by expanding by lineWidth, do not align to pixel boundries.

The article itself makes that mistake

strokeRect() is called to create a rectangular outline 50x50 pixels within the cleared square.

It shouldn't teach false info. The rectangle drawn is 52x52 pixels, and, they're half as bright/dark as requested.

@greggman
Copy link
Contributor Author

greggman commented Feb 14, 2025

it only obscures our conceptual understanding of these APIs

I disgree. It's the opposite. Without this info you get a wrong understanding. It's doesn't obscure it. It clearifies it.

I think a diagram like this tells a lot.

Image

It shows you're not working with pixels your working with shapes. It shows the pixels are colored by how much the shape you specify covers them. Those are important for not mis-understanding the API

@Josh-Cena Josh-Cena added help wanted If you know something about this topic, we would love your help! and removed needs triage Triage needed by staff and/or partners. Automatically applied when an issue is opened. labels Feb 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Content:WebAPI Web API docs help wanted If you know something about this topic, we would love your help!
Projects
None yet
Development

No branches or pull requests

2 participants