3

Earlier on I noticed that strokeRect (and any other method that involved stroke such as lineTo) created a grey 2 px wide line instead of a 1px wide black line. After some Google searching I found that context.translate(0.5, 0.5) fixed this. but now fillRect (and like before any other method that involves fill) creates a black box with a grey border around it.

Does anyone know a good way to make it so that both fillRect and strokeRect have crisp edges with no grey borders? I also don't know whether or not I should use context.translate(0.5, 0.5) for images, as it seems like SVGs have crisp edges regardless of whether or not I translate.

Here is a jsfiddle demonstrating this: http://jsfiddle.net/Tysonzero/ydm21pkt/1/

Note that the bottom strokeRect is crisp while the top one is blurry, and the top fillRect is crisp while the bottom one is blurry.

semicolon
  • 2,530
  • 27
  • 37
  • They definitely are integers. I generally use `(100, 100, 100, 100)` or similar. – semicolon Jan 21 '15 at 01:44
  • 1
    Is your actual canvas size scaled in css? – Damon Smith Jan 21 '15 at 03:14
  • What do you mean? I don't directly set the canvas size in CSS, I set the width and the height in JavaScript and it auto re-sizes. I can assure you none of that stuff is the issue. [Here](http://stackoverflow.com/a/13294650/1513295) is the kind of thing I am talking about. – semicolon Jan 21 '15 at 04:52

1 Answers1

3

Strokes draw half-inside & half-outside the x,y coordinates. That's why you are seeing the blur with integer x,y and why it clears up when the x,y are offset by a half pixel. Here's more on why the blur occurs: http://www.mobtowers.com/html5-canvas-crisp-lines-every-time/

An easy way to make rects crisper is to add methods to your context instance that offset strokeRect & fillRect for best appearance:

var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;

// add pixel aligned versions of strokeRect & fillRect to this context instance
context.sRect=function(x,y,w,h){
  x=parseInt(x)+0.50;
  y=parseInt(y)+0.50;
  this.strokeRect(x,y,w,h);
}
context.fRect=function(x,y,w,h){
  x=parseInt(x);
  y=parseInt(y);
  context.fillRect(x,y,w,h);
}

context.strokeStyle = "#000000";
context.fillStyle = "#000000";

context.strokeRect(100, 50, 100, 100);
context.fillRect(300.5, 50.5, 100, 100);


context.sRect(100,200,100,100);
context.fRect(300.5,200,100,100);

context.fillText('Unadjusted',20,100);
context.fillText('Adjusted',20,250);
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=500 height=500></canvas>
markE
  • 102,905
  • 11
  • 164
  • 176
  • That makes sense, thanks! One question I do have though is why you are using 300.5 in your `fRect` example, isn't the point of `fRect` to avoid having to use floats? In the end I realize it gets parsed to 300, but I just thought that was a little weird. Also are these the exact functions to use that will make the outsides of `sRect`s and `fRect`s line up perfectly if given the same coordinates? In some situations that might be pretty important for aesthetic appeal. – semicolon Jan 21 '15 at 07:43
  • 1
    You're welcome! The 300.5 is just so the problem would occur so I could test if it was cured by fRect ;-) I use parseInt (as opposed to rounding) so all the adjustment will be pulled to the left. This gives the best shot at perfect alignment. But alignment will not always be perfect. One final addition might be a method that first fills a rect and then also strokes that same rect. In this case the fill would be let to bleed by 0.50 pixel on all 4 sides. The immediate stroke would overwrite any bleed. That allows for a perfectly filled+stroked rect. Good luck with your project! – markE Jan 21 '15 at 15:35
  • 1
    Thanks man! I really enjoy JS / Canvas, even if JS is quite quirky. I think I will go with the thing you just suggested, translating 0.5 for all objects, and using both stroke and fill for filled rects, but only stroke for unfilled rects. – semicolon Jan 21 '15 at 23:27
  • 1
    Yes, somewhat counter-intuitively ctx.strokeRect() and ctx.fillRect() have opposite behaviour in this recpect. Took me some time to figure out, too! – Per Lindberg Apr 01 '16 at 09:56