Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML5 canvas text anti-aliases in chrome, not in firefox

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:

firefox

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

chrome

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?

like image 635
TakingItCasual Avatar asked Dec 02 '25 20:12

TakingItCasual


1 Answers

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.

The Basics

Here is an example:

  • The font is converted to a bitmap font, basically a mono-spaced sprite-sheet, optimized size-wise (and converted here to a data-uri).
  • Important: when generated only ASCII characters from and including 32 (space) are rendered, up to and including char 128.
  • A custom function is made to parse each character in the string. The char is converted to an ASCII index and 32 is subtracted as we skipped that when making the sprite-sheet.
  • A region in the sprite-sheet is calculated and then rendered directly to canvas at current position (x + string-index * character-width, y as-is).

// 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>

How to deal with different colors (or gradients, patterns)

A couple of simple modifications can be made to allow for colors, gradients, patterns etc.

  • Create a offscreen-canvas the same size as the sprite-sheet
  • Draw in the image
  • Select composition mode "source-atop", select color (or gradient, pattern) and fill the entire offscreen-canvas. Now continue as before but use the offscreen-canvas as image source instead of the image itself.

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


Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!