我知道这个问题已经被问过很多次了,但是我尝试了几乎所有可以在网上找到的东西,但是无论我尝试了什么(以及使用什么组合),仍然无法在 Canvas 上正确呈现文本。
对于模糊的线条和形状问题,只需将+ 0.5px添加到坐标即可解决问题:但是,此解决方案似乎不适用于文本渲染。
注意:我从不使用CSS来设置 Canvas 的宽度和高度(只尝试过一次以检查在HTML和CSS中设置大小属性是否会改变任何内容)。同样问题似乎与浏览器无关。
我试过了 :
canvas.getContext('2d', {alpha:false})
禁用Alpha通道,这使我的大部分图层消失了而未解决问题在此处查看canvas和html字体渲染之间的比较:https://jsfiddle.net/balleronde/1e9a5xbf/
甚至有可能使 Canvas 中的文本像dom元素中的文本一样呈现吗?任何意见或建议将不胜感激
最佳答案
Canvas 上的DOM质量文本。
仔细看
如果放大DOM文本,您将看到以下内容(顶部是 Canvas ,底部是DOM,居中位置希望是像素大小(不在视网膜显示屏上))
如您所见,底部文本上有彩色部分。这是因为它是使用称为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)
一样简单。