I'm drawing a custom monospaced bit font to an HTML5 canvas with JavaScript, and I'm getting different results between Firefox and Chrome. Firefox is drawing it the way I prefer it:

While Chrome draws it with anti-aliasing that I can't figure out how to get rid of:

The HTML code with CSS and JavaScript to reproduce the issue is the following. (Font download).
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
@font-face {
font-family: tis-100-copy;
src: local("tis 100 copy"),
local("tis-100-copy"),
url(tis100copy.ttf);
font-weight: bold;
}
html {
position: relative;
background-color: #DDDDDD;
font-family: tis-100-copy;
}
canvas#game {
position: relative;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
}
</style>
</head>
<body>
<canvas id="game" width="1366" height="768"></canvas>
<script>
var canvas = document.getElementById("game");
var ctx = canvas.getContext("2d");
//ctx.translate(0.5, 0.5); // Just causes both browsers to anti-alias
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
ctx.font = "12pt tis-100-copy";
function gameLoop() {
ctx.beginPath();
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#FFFFFF";
ctx.fillText("ThE qUiCk BrOwN fOx JuMpS oVeR tHe LaZy DoG.", 50, 50);
ctx.fillText("1234567890", 50, 62);
ctx.fillText("!#%()+,-./:<=>?[\\]_", 50, 74);
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
</script>
</body>
</html>
How can I get Chrome to cleanly draw the text without anti-aliasing?
To solve this problem to achieve cross-browser compatibility, and considering it's used apparently for a game, I would suggest a different approach by converting and using it as a bitmap font instead.
You can convert the font in question to a sprite-sheet and then build a simple custom function to render the text.
The process is simple and the performance in more than adequate if there is not large amount of text that needs to be rendered.
Here is an example:
// Note: font sprite-sheet premade using:
// https://jsfiddle.net/epistemex/bdm3tbtu/
var ctx, cw = 8, ch = 19, img = new Image; img.onload = go; img.src = bmp;
function go() {
ctx = c.getContext("2d");
// Custom text drawing function demo:
myFillText(ctx, "My custom text fill function", 12, 8);
};
function myFillText(ctx, str, x, y) {
x |= 0; y |= 0; // force x/y to integer positions
for(var i = 0, ascii; i < str.length; i++) {
// get ASCII code but offset -32 to match sprite-sheet
ascii = str.charCodeAt(i) & 0xff - 32;
// look-up bitmap font sprite-sheet and draw directly to canvas
ctx.drawImage(img, ascii * cw, 0, cw, ch, x + i * cw, y, cw, ch);
}
}
<canvas id=c width=600></canvas>
<script>
var bmp = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAvgAAAATBAMAAAAXEGS4AAAAG1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUUeIgAAAACHRSTlMAljxY3CJ2uKuwIxoAAANySURBVFjD7VhNa9tAEH2SZUlH0ULPatJSH00/IEdBc8jREAg6OqSBHg29+LiR43R+djMfm9VXKKREDcRPrFc7mtGbeVptNsIBBxxwwP/GrMQBEyO53kNAt1BsdydoISrwPJgRbTA9Eq2nnpS7LiKWct/P5dte+6ujSsWu6q/t628vKzwH4htgUWJyfL7W3mE6xA0ioFcvAVnpkDtCfVwWYGRF8sku5pzg6RnGkBMBOfeOm/gT28hx77w9gDTOQe0f2FRZDIEsjpuMmZ/UmVx+b9eMuBkHfJ9bb4f52WG5CDimiCA4lpQm4Ue0QaTatgRkOwq95fcSgqz6deFVIkt4CEsCD3TI5ZetHfJA5gWA2ivURHf3Zj2srDASRw1QC8AyELyv9ebrKB8vHiSxzshnZbpq1T8JP1Kn4ifrrvgMfjoxLTkImFF5tgO8ksm7y4r7DvTmNhvIFPVnXfGZO9uL0YfKIC3jHTWhFD0gAhD6xXPTPGlQevAdKZ7V1FgTPwIKGAjT8GNm4ufLgfjiiuRGxVqcol6ZRjyOChsECI28a+jJHRYXckH8sNyYR87J5Oswj+DLBzceP1o8jRWPQfGSnnIpt9XzBrgKVUzDj2yt4qebjoZB/NnPDTumm5PlF4RkbX3XEQUlKecfczQivqwuZH8LqE9lwcAFfoAKEp+H4imMiVrFkwgonCIA1JfgY7rFW24S5+v392qDMA3/vIGKj8VqQev5iPhHDY/rMtkXPfFtYDAiYoJQFCdl9PZwtGcMZj4B57gD0WMzjzqvsl+P5Sy34pXtvheeQfFBeGnhCQRMxo9kqeLHFQpsb/tJuIvZx4YdT8pYL2pGI+IbF9NTeEi+tZM2RyNB517n8+axNVesw+L1l9AqPqfhmmv+ZhPROjkGTMbfX/MHM78+f19xwKKKaZN5hdu+zvdiHux2tO+Ir+beAyRt6XyXUUojuw2gX1DwCDLYmzT0JdcpXj00gaH4k/D/Xfw53YLVTOl3THuYvOPiOygxSQv7Ye7966bnECRNe59v4mPb0B0th/tsgAdWiDRyYa0N+2zAXnzlDdziZfHtfT4PBuJPwY+osq1m8aRvO0z5dKQjdDIJMvwLpLonQARRTMLf/idrcmRjKb7RyfgqMN+p+CcrvAxkFbDFK0GuH9ZezlyriV7RV+wIAX8AnAnL3b7xqFYAAAAASUVORK5CYII=";
</script>
A couple of simple modifications can be made to allow for colors, gradients, patterns etc.
// Note: font sprite-sheet premade using:
// https://jsfiddle.net/epistemex/bdm3tbtu/
var cw = 8, ch = 19, c2, img = new Image; img.onload = go; img.src = bmp;
var ctx = c.getContext("2d"), ctx2;
function go() {
// setup offscreen-canvas
c2 = document.createElement("canvas");
c2.width = this.width; c2.height = this.height;
ctx2 = c2.getContext("2d");
ctx2.drawImage(this, 0, 0);
myFontColor("#c00");
myFillText(ctx, "My custom text fill function in red...", 12, 8);
};
function myFontColor(style) {
ctx2.globalCompositeOperation = "source-atop";
ctx2.fillStyle = style;
ctx2.fillRect(0, 0, c2.width, c2.height);
}
function myFillText(ctx, str, x, y) {
x |= 0; y |= 0;
for(var i = 0, ascii; i < str.length; i++) {
ascii = str.charCodeAt(i) & 0xff - 32;
ctx.drawImage(c2, ascii * cw, 0, cw, ch, x + i * cw, y, cw, ch);
}
}
<canvas id=c width=600></canvas>
<script>
var bmp = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAvgAAAATBAMAAAAXEGS4AAAAG1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUUeIgAAAACHRSTlMAljxY3CJ2uKuwIxoAAANySURBVFjD7VhNa9tAEH2SZUlH0ULPatJSH00/IEdBc8jREAg6OqSBHg29+LiR43R+djMfm9VXKKREDcRPrFc7mtGbeVptNsIBBxxwwP/GrMQBEyO53kNAt1BsdydoISrwPJgRbTA9Eq2nnpS7LiKWct/P5dte+6ujSsWu6q/t628vKzwH4htgUWJyfL7W3mE6xA0ioFcvAVnpkDtCfVwWYGRF8sku5pzg6RnGkBMBOfeOm/gT28hx77w9gDTOQe0f2FRZDIEsjpuMmZ/UmVx+b9eMuBkHfJ9bb4f52WG5CDimiCA4lpQm4Ue0QaTatgRkOwq95fcSgqz6deFVIkt4CEsCD3TI5ZetHfJA5gWA2ivURHf3Zj2srDASRw1QC8AyELyv9ebrKB8vHiSxzshnZbpq1T8JP1Kn4ifrrvgMfjoxLTkImFF5tgO8ksm7y4r7DvTmNhvIFPVnXfGZO9uL0YfKIC3jHTWhFD0gAhD6xXPTPGlQevAdKZ7V1FgTPwIKGAjT8GNm4ufLgfjiiuRGxVqcol6ZRjyOChsECI28a+jJHRYXckH8sNyYR87J5Oswj+DLBzceP1o8jRWPQfGSnnIpt9XzBrgKVUzDj2yt4qebjoZB/NnPDTumm5PlF4RkbX3XEQUlKecfczQivqwuZH8LqE9lwcAFfoAKEp+H4imMiVrFkwgonCIA1JfgY7rFW24S5+v392qDMA3/vIGKj8VqQev5iPhHDY/rMtkXPfFtYDAiYoJQFCdl9PZwtGcMZj4B57gD0WMzjzqvsl+P5Sy34pXtvheeQfFBeGnhCQRMxo9kqeLHFQpsb/tJuIvZx4YdT8pYL2pGI+IbF9NTeEi+tZM2RyNB517n8+axNVesw+L1l9AqPqfhmmv+ZhPROjkGTMbfX/MHM78+f19xwKKKaZN5hdu+zvdiHux2tO+Ir+beAyRt6XyXUUojuw2gX1DwCDLYmzT0JdcpXj00gaH4k/D/Xfw53YLVTOl3THuYvOPiOygxSQv7Ye7966bnECRNe59v4mPb0B0th/tsgAdWiDRyYa0N+2zAXnzlDdziZfHtfT4PBuJPwY+osq1m8aRvO0z5dKQjdDIJMvwLpLonQARRTMLf/idrcmRjKb7RyfgqMN+p+CcrvAxkFbDFK0GuH9ZezlyriV7RV+wIAX8AnAnL3b7xqFYAAAAASUVORK5CYII=";
</script>
Some optimization can be obtained by for example prerender common words into a separate sprite-sheet, then detect for this in the custom text function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With