微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

javascript – 画布文字渲染(模糊)

我知道这个问题已被多次询问,但是我尝试了几乎所有我能在网上找到的东西,但无论在什么样的(以及任何组合)我尝试过,仍然无法在画布上正确渲染文本.

对于模糊的线条和形状问题,简单地向坐标添加0.5px解决了问题:但是,此解决方案似乎不适用于文本渲染.

注意:我从不使用CSS来设置画布宽度和高度(只试过一次以检查HTML和CSS中的设置大小属性是否会改变任何东西).此外,问题似乎与浏览器无关.

我试过了 :

>使用HTML创建画布,然后使用javascript而不是html
>在HTML元素中设置宽度和高度,然后使用JS,然后使用HTML和JS
>使用每种可能的组合将0.5px添加到文本坐标
>更改font-family和font-size
>更改字体大小单位(px,pt,em)
>用不同的浏览器打开文件以检查是否有任何变化
>使用canvas.getContext(‘2d’,{alpha:false})禁用alpha通道,这使得我的大多数图层都消失了而没有解决问题

请参阅此处的canvas和html字体呈现之间的比较:https://jsfiddle.net/balleronde/1e9a5xbf/

甚至可以将画布中的文本渲染为dom元素中的文本吗?任何建议或建议将不胜感激

解决方法

画布上的DOM质量文本.

仔细看看

如果您放大DOM文本,您将看到以下内容(顶部是画布,底部是DOM,中心有望像素大小(不在视网膜显示器上))

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

Note using true type is an optional setting on browsers and operating systems. If you have it turned off or have a very low res device the zoomed text above will look the same (no coloured pixels in the bottom image)

像素和子像素

当您仔细观察LCD显示器时,您会看到每个像素由连续排列的3个子像素组成,每个像素分别用于红色,绿色和蓝色.要设置像素,请为每个颜色通道提供RGB强度,并设置相应的RGB子像素.我们通常认为红色是第一个,最后是蓝色,但实际情况是,只要它们彼此接近就会得到相同的结果,颜色的顺序无关紧要.

当您停止考虑颜色和几乎可控制的图像元素时,您的设备的水平分辨率将增加三倍.由于大多数文本都是单色的,因此您不必过于担心RGB子像素的对齐,您可以将文本渲染到子像素而不是整个像素,从而获得高质量的文本.子像素是如此之小,大多数人都没有注意到轻微的颜色扭曲,这种好处值得稍微肮脏的外观.

为什么没有真正的画布类型

使用子像素时,您需要完全控制每个子像素,包括alpha值.对于显示驱动程序alpha应用于像素的所有子像素,您不能在alpha为0.2时为蓝色,在alpha 0.7为相同像素时为红色.但是如果你知道每个子像素下的子像素值是什么,你可以进行alpha计算,而不是让硬件这样做.这可以让你在亚像素级别进行algha控制.

不幸的是(没有……幸运的是99.99%的情况)画布允许透明度,但你无法知道画布下的子像素在做什么,它们可以是任何颜色,因此你不能进行alpha计算需要有效地使用子像素.

本土亚像素文本.

但是你不必拥有透明画布,如果你使所有像素都不透明(alpha = 1.0),你就会重新获得亚像素alpha控制.

以下函数使用子像素绘制画布文本.它不是很快但它确实获得了更好的文本质量.

它的工作原理是将文本渲染为正常宽度的3倍.然后它使用额外的像素来计运算符像素值,并在完成时将子像素数据放到画布上.

Update When I wrote this answer I totaly forgot about zoom settings. Using sub pixels requiers a presise match between display physical pixel size and DOM pixel size. If you have zoomed in or out this will not be so and thus locating sub pixels becomes much more difficult.

I have updated the demos to try to detect the zoom settings. As there is not standard way to do this I have just used devicePixelRatio which for FF and Chrome are !== 1 when zoomed (And as I dont have a retina decvice I am only guessing if the bottom demo works). If you wish to see the demo correctly and you do not get a zoom warning though are still zoomed set the zoom to 1.

Addistionaly you may wish to set the zoom to 200% and use the bottom demo as it seems that zooming in reduces the DOM text quality considerably,while the canvas sub pixel maintains the high quality.

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

请注意,如果您有Retina显示屏或非常高分辨率的显示器,如果您没有看到高质量的画布文本,您应该查看此下方的片段.

标准的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,1,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,fontHeight );
    c.ctx.fillText(text,hOffset);    // draw thw text 3 time the width
    // convert to sub pixel 
    c.ctx.putimageData(subPixelBitmap(c.ctx.getimageData(0,width*3,fontHeight)),0);
    ctx.drawImage(c,width-1,y-hOffset,fontHeight);
    // done
}


var globalTime;
// render loop does the drawing
function update(timer) { // Main update loop
    globalTime = timer;
    ctx.setTransform(1,0); // set default
    ctx.globalAlpha= 1;
    ctx.fillStyle = "White";
    ctx.fillRect(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),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,canvas.height)
    ctx.fillStyle = "black";
    ctx.fillText("normal text is Oh hum "+ globalTime.toFixed(0),12,40);
    subPixelText(ctx,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的标准矢量文本渲染的示例可以在WebGL PDF找到

除了2D canvas API之外,WebGL API也很乐意将webGl渲染内容的结果复制到2D画布就像渲染imagecontext.drawImage(canvasWebGL,0)一样简单

原文地址:https://www.jb51.cc/js/157601.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐