admin管理员组

文章数量:1392003

I am using clearRect on a HTML5 canvas to redraw a rectangle. When using floating point coordinates the clearRect leaves a border from my rectangle on the canvas.

The following code demonstrates the problem with the rectangle using integer coordinates being fully cleared while the one using floating point leaves a border.

<html>
<head>
</head>
<body>
<script type="text/javascript" >

var canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
canvas.style.border = "1px solid";
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.fillRect(20.1,20.1,30,30);
ctx.clearRect(20.1,20.1,30,30);

ctx.fillRect(50,50,30,30);
ctx.clearRect(50,50,30,30);

</script>
</body>
</html>

The resulting canvas looks like this:

I can fix this by clearing a larger region, but that increases the risk of clearing and having to redraw adjacent shapes. This is for example suggested here: I can't pletely clear the transformed rectangle in <canvas>

I can fix it by using integer coordinates, but that is not an option in this application.

Are there other ways to make clearRect actually clear all of the drawn rectangle without clearing a larger region or using integer coordinates?

I am using clearRect on a HTML5 canvas to redraw a rectangle. When using floating point coordinates the clearRect leaves a border from my rectangle on the canvas.

The following code demonstrates the problem with the rectangle using integer coordinates being fully cleared while the one using floating point leaves a border.

<html>
<head>
</head>
<body>
<script type="text/javascript" >

var canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
canvas.style.border = "1px solid";
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.fillRect(20.1,20.1,30,30);
ctx.clearRect(20.1,20.1,30,30);

ctx.fillRect(50,50,30,30);
ctx.clearRect(50,50,30,30);

</script>
</body>
</html>

The resulting canvas looks like this:

I can fix this by clearing a larger region, but that increases the risk of clearing and having to redraw adjacent shapes. This is for example suggested here: I can't pletely clear the transformed rectangle in <canvas>

I can fix it by using integer coordinates, but that is not an option in this application.

Are there other ways to make clearRect actually clear all of the drawn rectangle without clearing a larger region or using integer coordinates?

Share Improve this question edited May 23, 2017 at 11:52 CommunityBot 11 silver badge asked Feb 20, 2014 at 9:35 K ErlandssonK Erlandsson 13.7k6 gold badges52 silver badges67 bronze badges 7
  • ok, so i saw i was quite almost as confusing as informing :-) could you elaborate on 'that is not an option in this application' ? why couldn't you restrict coordinates to always be integer + 0.5 ?? – GameAlchemist Commented Feb 20, 2014 at 20:42
  • I'm using a third party physics engine (box2dweb) in my app which uses floating point coordinates and sizes. However, I could of course round the coordinates at drawing time. I was thinking it would look weird doing that with shapes being more far apart than they should and such. But I should probably give it a try before dismissing it seeing there are no other really good options. – K Erlandsson Commented Feb 21, 2014 at 8:02
  • Maybe that's what you meant, but do not 'round coordinates at drawing time' : rather draw at rounded coordinates. Otherwise the engine will get lost. See the end of my post. I wonder : Why would you want to erase a given rect ? – GameAlchemist Commented Feb 21, 2014 at 11:04
  • Yes, draw the rounded coordinates is what I meant. I want to erase the rect since it is moving. I erase the last drawn rect and draw a new one with the new coordinates. – K Erlandsson Commented Feb 21, 2014 at 13:05
  • clearing whole canvas and redrawing everything is the way to go. Imagine the troubles you'll run into when you'll numerous object of various shape that overlaps ?? :-/ – GameAlchemist Commented Feb 21, 2014 at 14:35
 |  Show 2 more ments

2 Answers 2

Reset to default 4

All points in canvas are in fact centered in their middle coordinates (0.5, 0.5).
If you want to draw a black line one pixel thick, you'll have to draw it with centered coordinates.
If you draw it on an integer boundary, you'll in fact draw a two pixel thick lines both with lower opacity, leading to a thicker line drawn in dark gray instead of black :

Here's a picture showing this, zoomed 3 times :

More generally, any coordinates off the 0.5 boundary will be drawn with an opacity proportional to its distance to mid point.
Here's a set of horizontal line segments starting on an integer boundary, then shifted 1/10th of a pixel every 20 pixels :

zoomed 4 times :

We can see that we really have a 1 pixel line only when centered.

For your issue, there's no way you 'partially' clear a pixel : pixel is the ultimate unit here, so since colors have already been mixed, you can only either clear whole pixel, or just attenuate its intensity (which is the result you see).

I can think of two solutions :

  • rather than clearing, redraw everything except what you don't want any more. For this you have to handle some kind of scene graph, meaning : you need to have a collection of all the objects that needs drawing (held within an array for instance), and at draw time, you erase everything, redraw everything except the rectangle.

  • handle a bigger canvas behind the scene, that will have a higher resolution than the user canvas. This is were you draw, with better quality, and after drawing you copy it to the low-resolution user canvas. Draw on 0.5 boundaries with integer sizes (width/height of your rect for instance). This main canvas might be 4 or 8 times bigger. The maximum size of the canvas is limited, so watch out for this if you target all browsers, they do not all allow the same max size (below 6400X6400 should be fine, but not sure about it). You can handle multiples backstage canvas to go beyond that limit, with a little bit of extra work.

(Rq for solution 2 : be sure to disable image smoothing before copying to avoid artifacts).

(( the fiddle for the drawings is here : jsbin./xucuxoxo/1/ ))

Edit : it is a good practice to translate the context from (0.5;0.5) right after you created it. Then you will always draw integer coordinates. This way, you ensure that all, say, 1 pixel thick line will actually be drawn one pixel thick. Test rounding with floor or ceil, and choose the one you prefer.

Html canvas always applies anti-aliasing to "cure the jaggies".

Anti-aliasing visually smooths lines by adding semi-transparent pixels along the line so the eye is fooled into seeing a less-jagged line.

When you draw your rectangles, these semi-transparent pixels are automatically being applied outside the 30,30 area of your rectangles.

This means your 30x30 rectangle is actually slightly larger than 30x30.

When you do context.clearRect the browser does not clear those extra semi-transparent pixels.

That's why the uncleared pixels appear "ghostly" -- they are semi-transparent.

Unfortunately, there is no way currently to turn off anti-aliasing for html canvas primitive drawing (lines, etc).

You have discovered the 2 fastest solutions:

  • round pixel drawing coordinates to integers
  • clear an area slightly larger than the original drawing

You can draw without anti-aliasing by drawing pixels manually using getImageData/putImageData. This manual method works but is costly to performance. The decreased performance defeats the purpose of clearing just the drawn area.

Bottom line: You've already discovered the best solutions canvas currently has to offer :-(

本文标签: htmlJavaScript canvas clearRect leaves borders when using floating point coordinatesStack Overflow