Canvas text rendering (blurry)

后端 未结 4 2025
难免孤独
难免孤独 2020-12-03 08:14

I know this question has been asked many times, but I tried pretty much everything I could find on the net and still can\'t get the text to render properly in canvas no matt

4条回答
  •  孤城傲影
    2020-12-03 08:53

    Thanks a lot for all those explanations!

    It's quite incredible that displaying a "simple string" in canvas in a neat manner is not supported by the default fillText(), and that we have to do such tricks to have a proper display, that's to say a display which is not a bit blurry or fuzzy. It's somehow like the "1px line drawing issue" in canvas (for which making +0.5 to coordinates helps but without solving completely the issue)...

    I modified the code you provided above to make it support colored text (not only black and white text). I hope it can help.

    In the function subPixelBitmap(), there is a little algorithm to average red/green/blue colors. It improves a little the string display in canvas (on Chrome), especially for small fonts. Maybe there are other algos which are even better: if you find one, I would be interested.

    This figure shows the effect on display: Improved string display in canvas

    Here is a working example that can be run online: working example on jsfiddle.net

    The associated code is this one (check the above working example for the last version):

      canvas = document.getElementById("my_canvas");
      ctx = canvas.getContext("2d");
      ...
    
      // Display a string:
      // - nice way:
      ctx.font = "12px Arial";
      ctx.fillStyle = "red";
      subPixelText(ctx,"Hello World",50,50,25);  
      ctx.font = "bold 14px Arial";
      ctx.fillStyle = "red";
      subPixelText(ctx,"Hello World",50,75,25);
      // - blurry default way:  
      ctx.font = "12px Arial";
      ctx.fillStyle = "red";
      ctx.fillText("Hello World", 50, 100);    
      ctx.font = "bold 14px Arial";
      ctx.fillStyle = "red";
      ctx.fillText("Hello World", 50, 125);
    
    var subPixelBitmap = function(imgData){
        var spR,spG,spB; // sub pixels
        var id,id1; // pixel indexes
        var w = imgData.width;
        var h = imgData.height;
        var d = imgData.data;
        var x,y;
        var ww = w*4;
        for(y = 0; y < h; y+=1){ // (go through all y pixels)
            for(x = 0; x < w-2; x+=3){ // (go through all groups of 3 x pixels)
                var id = y*ww+x*4; // (4 consecutive values: id->red, id+1->green, id+2->blue, id+3->alpha)
                var output_id = y*ww+Math.floor(x/3)*4;
                spR = Math.round((d[id + 0] + d[id + 4] + d[id + 8])/3);
                spG = Math.round((d[id + 1] + d[id + 5] + d[id + 9])/3);
                spB = Math.round((d[id + 2] + d[id + 6] + d[id + 10])/3);
                // console.log(d[id+0], d[id+1], d[id+2] + '|' + d[id+5], d[id+6], d[id+7] + '|' + d[id+9], d[id+10], d[id+11]);                        
                d[output_id] = spR;
                d[output_id+1] = spG;
                d[output_id+2] = spB;
                d[output_id+3] = 255; // alpha is always set to 255
            }
        }
        return imgData;
    }
    
    var subPixelText = function(ctx,text,x,y,fontHeight){
    
        var width = ctx.measureText(text).width + 12; // add some extra pixels
        var hOffset = Math.floor(fontHeight);
    
        var c = document.createElement("canvas");
        c.width  = width * 3; // scaling by 3
        c.height = fontHeight;
        c.ctx    = c.getContext("2d");    
        c.ctx.font = ctx.font;
        c.ctx.globalAlpha = ctx.globalAlpha;
        c.ctx.fillStyle = ctx.fillStyle;
        c.ctx.fontAlign = "left";
        c.ctx.setTransform(3,0,0,1,0,0); // scaling by 3
        c.ctx.imageSmoothingEnabled = false;    
        c.ctx.mozImageSmoothingEnabled = false; // (obsolete)
        c.ctx.webkitImageSmoothingEnabled = false;
        c.ctx.msImageSmoothingEnabled = false;
        c.ctx.oImageSmoothingEnabled = false; 
        // copy existing pixels to new canvas
        c.ctx.drawImage(ctx.canvas,x,y-hOffset,width,fontHeight,0,0,width,fontHeight);
        c.ctx.fillText(text,0,hOffset-3 /* (harcoded to -3 for letters like 'p', 'g', ..., could be improved) */); // draw the text 3 time the width
        // convert to sub pixels 
        c.ctx.putImageData(subPixelBitmap(c.ctx.getImageData(0,0,width*3,fontHeight)), 0, 0);
        ctx.drawImage(c,0,0,width-1,fontHeight,x,y-hOffset,width-1,fontHeight);
    }
    

提交回复
热议问题