2

Why is it that rendering gray text onto a canvas looks absolutely terrible? In my example, the first piece of text looks great, the second piece looks awful, and the third piece looks passable.

Here is a screenshot of what I see:

Screenshot

var g = document.getElementById('canvas').getContext('2d');

g.font = "12px arial";

g.fillStyle = "#bbbbbb";
g.fillText("This is some example text. (CANVAS, gray)", 0, 30);

g.fillStyle = "black";
g.fillText("This is some example text. (CANVAS, black)", 0, 60);
div {
  font-family: "arial";
  font-size: 12px;
  color: #bbbbbb;
}
<div>This is some example text. (DOM, gray)</div>
<canvas id="canvas" width="377" height="174"></canvas>

Before marking as a duplicate, please note that I've searched on StackOverflow and Google and the answers there are insufficient for the following reasons: - I'm not using a retina monitor -- my pixel ratio is 1, so this is not about using a larger canvas size. - I've tried rendering the font on the "half pixel", but that doesn't change anything. Rendering on the half pixel helps with things like lines, but not text.

Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
satnam
  • 10,719
  • 5
  • 32
  • 42
  • 1
    I don't see anything abnormal. What is your definition of "awful" in this case? – Chris Oct 25 '16 at 01:58
  • Chris, I just updated the post with a screenshot. – satnam Oct 25 '16 at 02:00
  • This is canvas predefined behavior? – Donald Wu Oct 25 '16 at 02:02
  • 2
    What operating system is this? What browser? Is Arial definitely installed? Looks fine to me. – Luke Oct 25 '16 at 02:02
  • 1
    Can't reproduce that defective text with your provided code. FWIW, it looks like it's double-drawn. – markE Oct 25 '16 at 02:18
  • I'm on a macbook pro. macOS Sierra v10.12. Chrome 54.0.2840.71 (64-bit). Arial is definitely installed. – satnam Oct 25 '16 at 02:25
  • 1
    This is system related and are artifacts from ClearType-ish anti-aliasing (or on Mac: "LCD font smoothing"). Adjust system settings to use a different anti-aliasing to see if it changes. You can't do much with this though as this is something the browser chooses to apply this way (or the underlying text engine). It looks fine btw otherwise https://i.imgur.com/fp0Pe8J.png –  Oct 26 '16 at 01:11

1 Answers1

1

Awful text rendering fixes

Oh you have found the magic colour combination. Put it down on the long list of canvas problems.

Note I only found this problem on Chrome, Firefox does not have an issues. I can not use Edge as for some reason it over heats my laptop resulting in shutdown. So don't know about Edge.

The reason is because the canvas is transparent and the standard says that fully transparent pixels must be alpha multiplied making them black (This is to stop dumb image encoders from encoding transparent colour information that can not be seen). The renderer thinks that what is under the canvas is Black, because that is the colour of transparent pixels.

How to fix.

There are three solutions, nope scratch that the second solution does not work.

There are two solutions I can think of that involve less than 100 lines of code.

Draw a the background first then draw the text on top of that

The second solution I thought would be just draw a outline in a very low alpha value. But that still picks up the bad pre-multiplied transparent black

And the third is to fill (fillRect) the canvas with the text colour and then set the comp mode to "destination-in" and the draw the text. This allows you to still have the transparent pixels under the text

Snippet showing code.

var g = document.getElementById('canvas').getContext('2d');
g.font = "12px arial";
g.fillStyle = "#bbbbbb";
g.fillText("Very bad text rendering", 0, 12);

var g = document.getElementById('canvas1').getContext('2d');
g.font = "12px arial";
g.fillStyle = "#f3f5f6" 
g.fillRect(0,0,g.canvas.width,g.canvas.height);
g.fillStyle = "#bbbbbb";
g.fillText("Fixed with solid background", 0, 12);

var g = document.getElementById('canvas2').getContext('2d');
g.font = "12px arial";
g.strokeStyle = "rgba("+0xf3+","+0xf5+","+0xf6+",0.05)"; 
g.lineWidth = 2;
g.lineJoin = "round";
g.strokeText("Tried with pixel outline", 0, 12);
g.fillStyle = "#bbbbbb";
g.fillText("Tried with pixel outline", 0, 12);

var g = document.getElementById('canvas3').getContext('2d');
g.font = "12px arial";
g.fillStyle = "#bbbbbb";
g.fillRect(0,0,g.canvas.width,g.canvas.height);
g.globalCompositeOperation = "destination-in";
g.fillText("Fixed with comp op destination-in on text colour", 0, 12);
g.globalCompositeOperation = "source-over";
div {
  font-family: "arial";
  font-size: 12px;
  color: #bbbbbb;
  background : #f3f5f6;
}
<div>This is some example text. (DOM, gray)<br>
<canvas id="canvas" width="377" height="18"></canvas>
<canvas id="canvas1" width="377" height="18"></canvas>
<canvas id="canvas2" width="377" height="18"></canvas>
<canvas id="canvas3" width="377" height="18"></canvas>
</div>
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • The solution of drawing a background seems to work decently well. Still looks nowhere as good as the DOM rendering. Any idea how to make it look as good as the DOM? – satnam Oct 25 '16 at 03:09
  • Also, I miss JSFiddle. Ever since stackoverflow switched over to the new "snippets" you can no longer edit and play with other people's code. Is there a way to play with a snippet or it is read-only? – satnam Oct 25 '16 at 03:09
  • @satnam This answer http://stackoverflow.com/a/40074278/3877726 will improve text but there is no perfect solution. Personally I render text to canvas and then process it to death, resulting in a very high quality bitmaped text. But that gets nothing but down votes (bitmap text cant be this that etc..) so above link is best I have. As for trying code, just click "copy snippet to an answer" play with it in that answer and discard the answer when done. – Blindman67 Oct 25 '16 at 03:48