Rendering gray text on canvas looks awful - javascript

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:
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.

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>

Related

cavsTxt.measureText gives different width for text based on its font family and font size

I want to find the width of the text element using cavsTxt.measureText. Based on the width, I will check whether it is overflow from the parent element. I'm getting different width while changing the font-family and its size.
cavsElem = document.createElement("canvas");
cavsTxt = cavsElem.getContext("2d");
//when font family as Calibri and size as 11pt
cavsTxt.font = "11pt Calibri normal";
width = cavsTxt.measureText("Clear green fluorite with tiny crystals of
pyrite on top").width // 313.88671875
//when font family as arial and size as 10pt
cavsTxt.font = "13px arial normal";
width = cavsTxt.measureText("Clear green fluorite with tiny crystals of
pyrite on top").width // 285.4099426269531
is it behavior or an issue? how to achieve my requirement.
Yes you will get different size with different fonts.
Just visualize it to see what it is doing, here is a sample based on your code:
var text = "Clear green fluorite with tiny crystals of pyrite on top ."
var canvas = document.getElementById("canvas");
cavsTxt = canvas.getContext("2d");
function drawText(font, x, y) {
cavsTxt.beginPath();
cavsTxt.font = font;
width = cavsTxt.measureText(text).width
cavsTxt.lineWidth = 20;
cavsTxt.strokeStyle = "red";
cavsTxt.moveTo(x+5, y+15);
cavsTxt.lineTo(width + x+5, y+15);
cavsTxt.stroke();
cavsTxt.fillText(cavsTxt.font, x, y);
cavsTxt.fillText(text, x+5, y+20);
cavsTxt.fillText(width, width+15, y+20);
}
//when font family as Calibri and size as 11pt
drawText("11pt Calibri normal", 5, 15);
//when font family as arial and size as 10pt
drawText("13px arial normal", 5, 55);
drawText("13px arial", 5, 95);
drawText("bold 13px arial", 5, 140);
<canvas id="canvas" height=175 width=500 style="border:1px solid #000000;">
</canvas>
With that you can detect if the text will fit on the parent or do some special effects

Keep text, dynamically updated, centered within a canvas

I have the following canvas:
<canvas id="myCanvas" width="400" height="400" style="border:0px;">
Your browser does not support the HTML5 canvas tag.</canvas>
javascript:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(200, 200, 100, 0, 2 * Math.PI);
ctx.stroke();
I want to write a score within the center of the canvas:
var score = 0;score++;
ctx.fillText(score,200,200);
However this score will be updated and the more digit this value will be the less centered it will be.
I attempted to use the solution offered here, but the "text" here, is merely a javascript variable named score and thus not considered a text.
Here is a fiddle where I played around to try to solve my problem:
https://jsfiddle.net/27xfvLhh/
I see two potential way to obtain the result wanted:
Is there a way to fill the text directly centered within my canvas?
Is there a way to calculate what will be the size of the text?
Thanks
Is there a way to fill the text directly centered within my canvas?
You can use the properties textAlign for horizontal alignment and textBaseline for vertical alignment. Then calculate center point by for example using half the size of the canvas:
Is there a way to calculate what will be the size of the text?
The ctx.measureText("Text").width will give you the width of the text in pixels using the current set font.
var ctx = c.getContext("2d");
ctx.font = "32px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Some Centered Text", ctx.canvas.width>>1, ctx.canvas.height>>1);
// measure text
var width = ctx.measureText("Some Centered Text").width;
ctx.font = "20px sans-serif";
ctx.fillText(width + "px", ctx.canvas.width>>1, 90 + ctx.canvas.height>>1);
#c {border:1px solid}
<canvas id=c width=600 height=300></canvas>

Why does the Canvas.FillText method seem to use the wrong coordinates?

I am rendering text onto a HTML-Canvas like so:
https://www.w3schools.com/code/tryit.asp?filename=FMSIQUFFO5PL
As you can see, the text is supposed to be rendered at 32, 32. But for some reasons, the string appears too high.
It this because the Canvas' coordinates start at the top left corner of the Canvas-Element and the String's coordinates at the lower left corner of the String?
How do I work around this issue so that I can render text at exactly the position I am expecting it to be?
By default the vertical text-alignment (baseline) is set to "alphabetic" which uses the general bottom of the text for the y coordinate.
You can change this behavior by setting textBaseline to "top".
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.font = "32px Arial";
ctx.textAlign = "left";
ctx.textBaseline = "top"; // change baseline property
ctx.fillText("Hello World", 32, 32);
<canvas id="myCanvas" style="border:1px solid #d3d3d3;"></canvas>

Text position on canvas is different in FireFox and Chrome

I need to draw a text string at a precise position on HTML5 canvas.
Here's my test code:
<!DOCTYPE html>
<html>
<body>
<canvas id="mainCanvas" width="320" height="240" style = "border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
window.onload = function() {
var canvas = document.getElementById("mainCanvas");
var ctx = canvas.getContext("2d");
ctx.textBaseline = "top";
ctx.font = '100px Arial';
ctx.textAlign = 'left';
ctx.fillStyle = 'rgba(0, 0, 0, 255)';
ctx.fillText('Test', 0, 0);
}
</script>
</body>
</html>
The margin at the top is different in Chrome and Firefox:
I'm going to draw other elements (e.g. images, shapes) on the canvas, and I need to make sure the text appears at the same position in all browsers. Is it possible?
Cause
As #iftah says: This mis-alignment is caused by a Firefox bug.
Please add your voice to the Firefox's bug page so they fix it soon.
Workaround
(Unfortunately), The workaround is to draw the text on a second canvas and scan that pixel data to find the topmost & leftmost pixel of the text.
Then draw the text on the main canvas but pulled upward and leftward by the calculated pixel offsets.
Here is annotated code and a Demo:
// canvas vars
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// test vars
var text='Test';
var fontsize=100;
var fontface='arial';
drawTextAtXY(0,0,text,fontsize,fontface,'black');
function drawTextAtXY(x,y,text,fontsize,fontface,fill){
// find the leftmost & topmost pixel of the text
var minXY=getTextTop(text,fontsize,fontface);
// set the font styles
ctx.textBaseline='top';
ctx.font=fontsize+'px '+fontface;
ctx.fillStyle=fill;
// draw the text
// Pull the text leftward and topward by minX & minY
ctx.fillText(text,x-minXY.x,y-minXY.y);
}
function getTextTop(text,fontsize,fontface){
// create temp working canvas
var c=document.createElement('canvas');
var w=canvas.width;
var h=fontsize*2;
c.width=w;
c.height=h;
var cctx=c.getContext('2d');
// set font styles
cctx.textBaseline='top';
cctx.font=fontsize+'px '+fontface;
cctx.fillStyle='red';
// draw the text
cctx.fillText(text,0,0);
// get pixel data
var imgdata=cctx.getImageData(0,0,w,h);
var d=imgdata.data;
// scan pixel data for minX,minY
var minX=10000000;
var minY=minX;
for(var y=0;y<h;y++){
for(var x=0;x<w;x++){
var n=(y*w+x)*4
if(d[n+3]>0){
if(y<minY){minY=y;}
if(x<minX){minX=x;}
}
}}
// return the leftmost & topmost pixel of the text
return({x:minX,y:minY});
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Text drawn exactly at specified X,Y</h4>
<canvas id="canvas" width=300 height=200></canvas>
You can fix it easily.
you can use textBaseline = "middle";
and must use transform to 50px of top
means:
<canvas id="mainCanvas" width="320" height="240" style = "border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
window.onload = function() {
var canvas = document.getElementById("mainCanvas");
var ctx = canvas.getContext("2d");
ctx.textBaseline = "middle";
ctx.font = '100px Arial';
ctx.textAlign = 'left';
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.save();
ctx.transform(1, 0, 0, 1, 0, 50);
ctx.fillText('Test', 0, 0);
ctx.restore();
}
</script>
A another bug is in your code: rgba(0, 0, 0, 255)
fourth number in rgba must be between 0-1 for example 0.75.
why I write .save() and .restore() ?
for reset transform and clear after use it.
Of course you can use
ctx.fillText('Test', 0, 50);
Instead
ctx.save();
ctx.transform(1, 0, 0, 1, 0, 50);
ctx.fillText('Test', 0, 0);
ctx.restore();
This appears to be a bug in Firefox dating back to 2012 and still not fixed!
See https://bugzilla.mozilla.org/show_bug.cgi?id=737852
Edit:
Setting the baseLine to "alphabetic" (the default) results in both Chrome and Firefox having the same vertical location for the text.
Edit:
Unfortunately, the vertical location isn't the only difference between Firefox and Chrome. Changing the string to "Testing" shows clearly that not only is the vertical location different, but also the space between the letters is slightly different - resulting in different width of the text.
If you really must have pixel perfect location AND size of the text maybe you should use Bitmap fonts
As stated in other answer it's some king of bug (or maybe just a different implementation). textBaseline bottom and Y position to 100 can fix it.
<canvas id="mainCanvas" width="320" height="240" style = "border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
window.onload = function() {
var canvas = document.getElementById("mainCanvas");
var ctx = canvas.getContext("2d");
ctx.textBaseline = "bottom";
ctx.font = '100px Arial';
ctx.textAlign = 'left';
ctx.fillStyle = 'rgba(150, 50, 0, 1)';
ctx.fillText('Test', 0, 100);
}
</script>
In general: Use baseline bottom and increase Y position the font size amount

html canvas shadow/blur won't go away

There is a shadow on the bottom and right sides of a rectangle I've drawn in the canvas on the upper-left hand corner of my window here:
http://thomasshouler.com/datavis/gugg/ratio.html
I haven't set any positive values to the relevant context attributes (shadowBlur, shadowOffsetY, etc.), so what gives?
Canvas code snippet:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.shadowBlur=0;
ctx.shadowOffsetY=0;
var width = 50;
var height = 4;
var grd=ctx.createLinearGradient(0,0,width,0);
grd.addColorStop(0,'#008000');
grd.addColorStop(0.5,'#CCCCCC');
grd.addColorStop(1,'#FF0000');
ctx.fillStyle=grd;
ctx.fillRect(0,0,width,height);
Any wisdom would be greatly appreciated.
It's related to scaling as the canvas is displayed. Your original fragment with a very small gradient and scaled up http://jsfiddle.net/CBzu4/ clearly shows the blurred outline.
Drawing larger (making sure there is no css scaling) it looks fine: http://jsfiddle.net/CBzu4/1/ and the code is the same as yours, just rearranged:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var width = 50 * 8;
var height = 4 * 8;
var grd=ctx.createLinearGradient(0,0,width,0);
grd.addColorStop(0,'#008000');
grd.addColorStop(0.5,'#FFFF00');
grd.addColorStop(1,'#FF0000');
ctx.fillStyle = grd;
ctx.fillRect(5,5,width,height);
Same code, but this time the canvas scaled up with the embedded style: http://jsfiddle.net/CBzu4/2/
<canvas id="myCanvas" style="border: 1px solid red; width: 120%; height: 120%;" width="600" height="80">
The final version is again the same as yours, no css scaling at all and no blurred outline: http://jsfiddle.net/CBzu4/3/

Categories