10

I'm drawing text on Canvas, and am disappointed with the quality of antialiasing. As far as I've been able to determine, browsers don't do subpixel antialising of text on Canvas.

Is this accurate?

This is particularly noticeable on iPhone and Android, where the resulting text isn't as crisp as text rendered by other DOM elements.

Any suggestions for high quality text out put on Canvas?

Joubert

Joubert Nel
  • 3,154
  • 3
  • 26
  • 24
  • 1
    possible duplicate of [Subpixel anti-aliased text on HTML5's canvas element](http://stackoverflow.com/questions/4550926/subpixel-anti-aliased-text-on-html5s-canvas-element) – Phrogz Mar 11 '11 at 00:47
  • I figured out the solution and wrote a blog post about it, since I suspect others have run into this as well: [http://joubert.posterous.com/crisp-html-5-canvas-text-on-mobile-phones-and](https://web.archive.org/web/20120427162431/http://joubert.posterous.com/crisp-html-5-canvas-text-on-mobile-phones-and) – Joubert Nel Mar 15 '11 at 02:41
  • Unfortunately that link is now broken, I'd love to know what it said. – James M Jun 19 '13 at 14:04
  • @JamesM: Fear not, here is is: https://web.archive.org/web/20120427162431/http://joubert.posterous.com/crisp-html-5-canvas-text-on-mobile-phones-and – Alix Axel Jan 07 '14 at 05:33
  • Unfortunately that link is also now broken. I'd also love to know what it said. – Kenzie Revoyr Jan 27 '21 at 12:29

4 Answers4

9

My answer came from this link, maybe it will help someone else. http://www.html5rocks.com/en/tutorials/canvas/hidpi/

The important code is as follows.

// finally query the various pixel ratios
    devicePixelRatio = window.devicePixelRatio || 1,
    backingStoreRatio = context.webkitBackingStorePixelRatio ||
                        context.mozBackingStorePixelRatio ||
                        context.msBackingStorePixelRatio ||
                        context.oBackingStorePixelRatio ||
                        context.backingStorePixelRatio || 1,

    ratio = devicePixelRatio / backingStoreRatio;

// upscale the canvas if the two ratios don't match
if (devicePixelRatio !== backingStoreRatio) {

    var oldWidth = canvas.width;
    var oldHeight = canvas.height;

    canvas.width = oldWidth * ratio;
    canvas.height = oldHeight * ratio;

    canvas.style.width = oldWidth + 'px';
    canvas.style.height = oldHeight + 'px';

    // now scale the context to counter
    // the fact that we've manually scaled
    // our canvas element
    context.scale(ratio, ratio);

}
cnotethegr8
  • 7,342
  • 8
  • 68
  • 104
3

Try adding the following META tag to your page. This seems to fix anti-aliasing issues I've had on iPhone Safari:

<meta name="viewport" content="user-scalable=no, width=device-width,
      initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5" />
chbrown
  • 11,865
  • 2
  • 52
  • 60
nick fallon
  • 351
  • 3
  • 2
1

I realise this is an old question, but I worked on this problem today and got it working nicely. I used Alix Axel's answer above and stripped down the code I found there (on the web.archive.org link) to the bare essentials.

I modified the solution a bit, using two canvases, one hidden canvas for the original text and a second canvas to actually show the anti-aliaised text.

Here's what I came up with... http://jsfiddle.net/X2cKa/

The code looks like this;

function alphaBlend(gamma, c1, c2, alpha) {
    c1 = c1/255.0;
    c2 = c2/255.0;
    var c3 = Math.pow(
    Math.pow(c1, gamma) * (1 - alpha)
        + Math.pow(c2, gamma) * alpha,
    1/gamma
    );
    return Math.round(c3 * 255);
 }

function process(textPixels, destPixels, fg, bg) {
    var gamma = 2.2;
    for (var y = 0; y < textPixels.height; y ++) {
        var history = [255, 255, 255];
        var pixel_number = y * textPixels.width;
        var component = 0;
        for (var x = 0; x < textPixels.width; x ++) {
            var alpha = textPixels.data[(y * textPixels.width + x) * 4 + 1] / 255.0;
            alpha = Math.pow(alpha, gamma);
            history[component] = alpha;
            alpha = (history[0] + history[1] + history[2]) / 3;
            out = alphaBlend(gamma, bg[component], fg[component], alpha);
            destPixels.data[pixel_number * 4 + component] = out;    
            /* advance to next component/pixel */
            component ++;
            if (component == 3) {
            pixel_number ++;
            component = 0;
            }
        }
    }
}

function toColor(colorString) {
    return [parseInt(colorString.substr(1, 2), 16),
    parseInt(colorString.substr(3, 2), 16),
    parseInt(colorString.substr(5, 2), 16)];
}

function renderOnce() {
    var phrase = "Corporate GOVERNANCE"
    var c1 = document.getElementById("c1"); //the hidden canvas
    var c2 = document.getElementById("c2"); //the canvas
    var textSize=40;

    var font = textSize+"px Arial"
    var fg = "#ff0000";
    var bg = "#fff9e1";

    var ctx1 = c1.getContext("2d");
    var ctx2 = c2.getContext("2d");
    ctx1.fillStyle = "rgb(255, 255, 255)";
    ctx1.fillRect(0, 0, c1.width, c1.height);

    ctx1.save();
    ctx1.scale(3, 1);
    ctx1.font = font;
    ctx1.fillStyle = "rgb(255, 0, 0)";
    ctx1.fillText(phrase, 0, textSize);
    ctx1.restore();

    var textPixels = ctx1.getImageData(0, 0, c1.width, c1.height);

    var colorFg = toColor(fg);
    var colorBg = toColor(bg);
    var destPixels3 = ctx1.getImageData(0, 0, c1.width, c1.height);
    process(textPixels, destPixels3, colorBg, colorFg);
    ctx2.putImageData(destPixels3, 0, 0);


    //for comparison, show Comparison Text without anti aliaising
    ctx2.font = font;
    ctx2.fillStyle = "rgb(255, 0, 0)";
    ctx2.fillText(phrase, 0, textSize*2);

};

renderOnce();

I also added a comparison text object so that you can see the anti-aliasing working.

Hope this helps someone!

gts101
  • 719
  • 7
  • 13
0

There is some subpixel antialiasing done, but it is up to the browser/OS.

There was a bit of an earlier discussion on this that may be of help to you.

I don't have an android or iOS device but just for kicks, try translating the context by (.5, 0) pixels before you draw and see if that makes a difference in how your text is rendered.

Community
  • 1
  • 1
Simon Sarris
  • 62,212
  • 13
  • 141
  • 171