我知道这个问题已经被问过很多次了,但是我尝试了几乎所有可以在网上找到的东西,但是无论我尝试了什么(以及使用什么组合),仍然无法在 Canvas 上正确呈现文本。

对于模糊的线条和形状问题,只需将+ 0.5px添加到坐标即可解决问题:但是,此解决方案似乎不适用于文本渲染。

注意:我从不使用CSS来设置 Canvas 的宽度和高度(只尝试过一次以检查在HTML和CSS中设置大小属性是否会改变任何内容)。同样问题似乎与浏览器无关。

我试过了 :

  • 使用HTML创建 Canvas ,然后使用javascript而不是html创建 Canvas
  • 在HTML元素中设置宽度和高度,然后使用JS,然后同时使用HTML和JS设置
  • 使用每种可能的组合
  • 向文本坐标添加0.5px
  • 更改字体系列和字体大小
  • 更改字体大小单位(px,pt,em)
  • 使用不同的浏览器打开文件以检查是否有任何更改
  • 使用canvas.getContext('2d', {alpha:false})禁用Alpha通道,这使我的大部分图层消失了而未解决问题

  • 在此处查看canvas和html字体渲染之间的比较:https://jsfiddle.net/balleronde/1e9a5xbf/

    甚至有可能使 Canvas 中的文本像dom元素中的文本一样呈现吗?任何意见或建议将不胜感激

    最佳答案

    Canvas 上的DOM质量文本。

    仔细看

    如果放大DOM文本,您将看到以下内容(顶部是 Canvas ,底部是DOM,居中位置希望是像素大小(不在视网膜显示屏上))

    javascript -  Canvas 文本渲染(模糊)-LMLPHP

    如您所见,底部文本上有彩色部分。这是因为它是使用称为true type的技术进行渲染的



    像素和子像素

    当您仔细观察LCD显示器时,您会发现每个像素由3个子像素组成,这些子像素连续排列,每个红色,绿色和蓝色。要设置像素,您需要为每个颜色通道提供RGB强度,并设置适当的RGB子像素。我们通常接受红色为第一,蓝色为最后,但现实情况是,只要颜色彼此接近,您得到相同的结果就无关紧要。

    当您停止考虑颜色以及可控制的图像元素时,设备的水平分辨率将增加三倍。由于大多数文本都是单色的,因此您不必太担心RGB子像素的对齐方式,并且可以将文本渲染到子像素而不是整个像素,从而获得高质量的文本。子像素是如此之小,以至于大多数人不会注意到轻微的颜色失真,而这样做的好处是值得稍微脏的外观。

    为什么 Canvas 没有真类型

    使用子像素时,您需要完全控制每个像素,包括alpha值。对于显示驱动程序,alpha适用于一个像素的所有子像素,因此,alpha 0.2不能具有蓝色,alpha 0.7不能在同一像素具有红色。但是,如果您知道每个子像素下的子像素值是多少,则可以执行alpha计算,而不是让硬件来进行。这使您可以在子像素级别上进行algha控制。

    不幸的是(幸运的是,在99.99%的情况下很幸运) Canvas 允许透明,但是您无法知道 Canvas 下的子像素在做什么,它们可以是任何颜色,因此您不能进行Alpha计算需要有效地使用子像素。

    本地增长的亚像素文本。

    但是,您不必具有透明的 Canvas ,并且如果使所有像素都不透明(alpha = 1.0),则可以重新获得子像素alpha控件。

    以下函数使用子像素绘制 Canvas 文本。它的速度不是很快,但是可以得到质量更好的文本。

    它通过以正常宽度的3倍渲染文本来工作。然后,它使用多余的像素来计算子像素值,完成后将子像素数据放到 Canvas 上。



    顶部文本是普通的Canvas文本,中心是 Canvas 上的(自制)子像素文本,底部是DOM文本

    请注意,如果您使用的是Retina显示屏或高分辨率显示屏,则如果看不到高质量的 Canvas 文本,则应查看该显示屏下面的代码段。

    标准的1到1像素演示。

    var createCanvas =function(w,h){
        var c = document.createElement("canvas");
        c.width  = w;
        c.height = h;
        c.ctx    = c.getContext("2d");
       // document.body.appendChild(c);
        return c;
    }
    
    // converts pixel data into sub pixel data
    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;
        var ww4 = ww+4;
        for(y = 0; y < h; y+=1){
            for(x = 0; x < w; x+=3){
                var id = y*ww+x*4;
                var id1 = Math.floor(y)*ww+Math.floor(x/3)*4;
                spR = Math.sqrt(d[id + 0] * d[id + 0] * 0.2126 + d[id + 1] * d[id + 1] * 0.7152 + d[id + 2] * d[id + 2] * 0.0722);
                id += 4;
                spG = Math.sqrt(d[id + 0] * d[id + 0] * 0.2126 + d[id + 1] * d[id + 1] * 0.7152 + d[id + 2] * d[id + 2] * 0.0722);
                id += 4;
                spB = Math.sqrt(d[id + 0] * d[id + 0] * 0.2126 + d[id + 1] * d[id + 1] * 0.7152 + d[id + 2] * d[id + 2] * 0.0722);
    
                d[id1++] = spR;
                d[id1++] = spG;
                d[id1++] = spB;
                d[id1++] = 255;  // alpha always 255
            }
        }
        return imgData;
    }
    
    // Assume default textBaseline and that text area is contained within the canvas (no bits hanging out)
    // Also this will not work is any pixels are at all transparent
    var subPixelText = function(ctx,text,x,y,fontHeight){
        var width = ctx.measureText(text).width + 12; // add some extra pixels
        var hOffset = Math.floor(fontHeight *0.7);
        var c = createCanvas(width * 3,fontHeight);
        c.ctx.font = ctx.font;
        c.ctx.fillStyle = ctx.fillStyle;
        c.ctx.fontAlign = "left";
        c.ctx.setTransform(3,0,0,1,0,0); // scale by 3
        // turn of smoothing
        c.ctx.imageSmoothingEnabled = false;
        c.ctx.mozImageSmoothingEnabled = false;
        // copy existing pixels to new canvas
        c.ctx.drawImage(ctx.canvas,x -2, y - hOffset, width,fontHeight,0,0, width,fontHeight );
        c.ctx.fillText(text,0,hOffset);    // draw thw text 3 time the width
        // convert to sub pixel
        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);
        // done
    }
    
    
    var globalTime;
    // render loop does the drawing
    function update(timer) { // Main update loop
        globalTime = timer;
        ctx.setTransform(1,0,0,1,0,0); // set default
        ctx.globalAlpha= 1;
        ctx.fillStyle = "White";
        ctx.fillRect(0,0,canvas.width,canvas.height)
        ctx.fillStyle = "black";
        ctx.fillText("Canvas text is Oh hum "+ globalTime.toFixed(0),6,20);
        subPixelText(ctx,"Sub pixel text is best "+ globalTime.toFixed(0),6,45,25);
        div.textContent = "DOM is off course perfect "+ globalTime.toFixed(0);
        requestAnimationFrame(update);
    }
    
    function start(){
        document.body.appendChild(canvas);
        document.body.appendChild(div);
        ctx.font = "20px Arial";
        requestAnimationFrame(update);  // start the render
    }
    
    var canvas = createCanvas(512,50); // create and add canvas
    var ctx = canvas.ctx;  // get a global context
    var div = document.createElement("div");
    div.style.font = "20px Arial";
    div.style.background = "white";
    div.style.color = "black";
    if(devicePixelRatio !== 1){
       var dir = "in"
       var more = "";
       if(devicePixelRatio > 1){
           dir = "out";
       }
       if(devicePixelRatio === 2){
           div.textContent = "Detected a zoom of 2. You may have a Retina display or zoomed in 200%. Please use the snippet below this one to view this demo correctly as it requiers a precise match between DOM pixel size and display physical pixel size. If you wish to see the demo anyways just click this text. ";
    
           more = "Use the demo below this one."
       }else{
           div.textContent = "Sorry your browser is zoomed "+dir+".This will not work when DOM pixels and Display physical pixel sizes do not match. If you wish to see the demo anyways just click this text.";
           more = "Sub pixel display does not work.";
       }
        document.body.appendChild(div);
        div.style.cursor = "pointer";
        div.title = "Click to start the demo.";
        div.addEventListener("click",function(){
            start();
            var divW = document.createElement("div");
            divW.textContent = "Warning pixel sizes do not match. " + more;
            divW.style.color = "red";
            document.body.appendChild(divW);
        });
    
    }else{
        start();
    }
    
    
    
    
    
    
              


    1至2像素比例演示。

    对于视网膜,高分辨率或缩放比例为200%的浏览器。

    var createCanvas =function(w,h){
        var c = document.createElement("canvas");
        c.width  = w;
        c.height = h;
        c.ctx    = c.getContext("2d");
       // document.body.appendChild(c);
        return c;
    }
    
    // converts pixel data into sub pixel data
    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;
        var ww4 = ww+4;
        for(y = 0; y < h; y+=1){
            for(x = 0; x < w; x+=3){
                var id = y*ww+x*4;
                var id1 = Math.floor(y)*ww+Math.floor(x/3)*4;
                spR = Math.sqrt(d[id + 0] * d[id + 0] * 0.2126 + d[id + 1] * d[id + 1] * 0.7152 + d[id + 2] * d[id + 2] * 0.0722);
                id += 4;
                spG = Math.sqrt(d[id + 0] * d[id + 0] * 0.2126 + d[id + 1] * d[id + 1] * 0.7152 + d[id + 2] * d[id + 2] * 0.0722);
                id += 4;
                spB = Math.sqrt(d[id + 0] * d[id + 0] * 0.2126 + d[id + 1] * d[id + 1] * 0.7152 + d[id + 2] * d[id + 2] * 0.0722);
    
                d[id1++] = spR;
                d[id1++] = spG;
                d[id1++] = spB;
                d[id1++] = 255;  // alpha always 255
            }
        }
        return imgData;
    }
    
    // Assume default textBaseline and that text area is contained within the canvas (no bits hanging out)
    // Also this will not work is any pixels are at all transparent
    var subPixelText = function(ctx,text,x,y,fontHeight){
        var width = ctx.measureText(text).width + 12; // add some extra pixels
        var hOffset = Math.floor(fontHeight *0.7);
        var c = createCanvas(width * 3,fontHeight);
        c.ctx.font = ctx.font;
        c.ctx.fillStyle = ctx.fillStyle;
        c.ctx.fontAlign = "left";
        c.ctx.setTransform(3,0,0,1,0,0); // scale by 3
        // turn of smoothing
        c.ctx.imageSmoothingEnabled = false;
        c.ctx.mozImageSmoothingEnabled = false;
        // copy existing pixels to new canvas
        c.ctx.drawImage(ctx.canvas,x -2, y - hOffset, width,fontHeight,0,0, width,fontHeight );
        c.ctx.fillText(text,0,hOffset);    // draw thw text 3 time the width
        // convert to sub pixel
        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);
        // done
    }
    
    
    var globalTime;
    // render loop does the drawing
    function update(timer) { // Main update loop
        globalTime = timer;
        ctx.setTransform(1,0,0,1,0,0); // set default
        ctx.globalAlpha= 1;
        ctx.fillStyle = "White";
        ctx.fillRect(0,0,canvas.width,canvas.height)
        ctx.fillStyle = "black";
        ctx.fillText("Normal text is Oh hum "+ globalTime.toFixed(0),12,40);
        subPixelText(ctx,"Sub pixel text is best "+ globalTime.toFixed(0),12,90,50);
        div.textContent = "DOM is off course perfect "+ globalTime.toFixed(0);
        requestAnimationFrame(update);
    }
    
    
    var canvas = createCanvas(1024,100); // create and add canvas
    canvas.style.width = "512px";
    canvas.style.height = "50px";
    var ctx = canvas.ctx;  // get a global context
    var div = document.createElement("div");
    div.style.font = "20px Arial";
    div.style.background = "white";
    div.style.color = "black";
    function start(){
        document.body.appendChild(canvas);
        document.body.appendChild(div);
        ctx.font = "40px Arial";
        requestAnimationFrame(update);  // start the render
    }
    
    if(devicePixelRatio !== 2){
       var dir = "in"
       var more = "";
       div.textContent = "Incorrect pixel size detected. Requiers zoom of 2. See the answer for more information. If you wish to see the demo anyways just click this text. ";
    
    
        document.body.appendChild(div);
        div.style.cursor = "pointer";
        div.title = "Click to start the demo.";
        div.addEventListener("click",function(){
            start();
            var divW = document.createElement("div");
            divW.textContent = "Warning pixel sizes do not match. ";
            divW.style.color = "red";
            document.body.appendChild(divW);
        });
    
    }else{
        start();
    }
    
    
    
    
    
              


    以获得更好的结果。

    为了获得最佳结果,您将需要使用webGL。这是从标准抗锯齿到子像素抗锯齿的相对简单的修改。可以在WebGL PDF中找到使用webGL进行标准 vector 文本渲染的示例

    WebGL API会很高兴地位于2D canvas API之外,并且将webGl渲染的内容的结果复制到2D canvas就像渲染图像context.drawImage(canvasWebGL,0,0)一样简单。

    10-05 20:45
    查看更多