3

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?

TakingItCasual
  • 771
  • 1
  • 7
  • 22
  • Have you tried using pixels rather than points. eg `ctx.font = "12px tis-100-copy";` – Blindman67 Nov 06 '17 at 15:54
  • To get the same sized text I had to use 16px, and no, it didn't fix the problem. – TakingItCasual Nov 06 '17 at 16:03
  • I have installed and tried out the font. There is a problem with the pixel size with the only size that has a one to one pixel size is 16px but the alignment is out (top left pixel of font in wrong position) The only other thing you can try is trying the alignment options (ctx.textAlign), and offsetting the font position by fractions of a pixel (step at 1/128th see if any eliminate the aliasing) if not use another font. – Blindman67 Nov 06 '17 at 17:21

2 Answers2

2

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.

Community
  • 1
  • 1
  • Define "large amount of text". There can be up to about 4000 characters on-screen at once. (I'll only need to redraw, on average, a few dozen each frame though.) – – TakingItCasual Nov 07 '17 at 15:17
  • 1
    @TakingItCasual 4000 should work (per frame) but I don't have any performance test (I base this on making a particle system a few years back which could produce 10,000+ sprites including resizing, anti-aliasing etc.); you'll have to make one - you will in any case notice when you're close the limit (and make some slack-buffer for that as it will be different from computer to computer). If the intention is to show the same text over some period of time I would recommend to cache the text to a offscreen canvas instead and just draw that canvas on top in a single draw operation until next change. –  Nov 08 '17 at 05:10
0

Check out the answers here, you might find something helpful. Unfortunately I can't try it with your font, so I couldn't find out more.

Zamfi
  • 327
  • 2
  • 9