Nor*_*ora 9 javascript html5 text rendering canvas
我知道这个问题已被多次询问过,但是我尝试了几乎所有我能在网上找到的东西,但无论在什么样的(以及任何组合)我尝试过,仍无法在画布上正确渲染文本.
对于模糊的线条和形状问题,只需在坐标上添加+ 0.5px即可解决问题:但是,此解决方案似乎不适用于文本渲染.
注意:我从不使用CSS来设置画布宽度和高度(只需尝试一次以检查HTML和CSS中的设置大小属性是否会改变任何内容).此外,问题似乎与浏览器无关.
我试过了 :
canvas.getContext('2d', {alpha:false}),只是让我的大部分图层消失而不解决问题请参阅canvas和html字体渲染之间的比较:https://jsfiddle.net/balleronde/1e9a5xbf/
甚至可以将画布中的文本渲染为dom元素中的文本吗?任何建议或建议将不胜感激
Bli*_*n67 14
仔细看看
如果您放大DOM文本,您将看到以下内容(顶部是画布,底部是DOM,中心有望像素大小(不在视网膜显示器上))
如您所见,底部文本上有彩色部分.这是因为它使用了一种名为true type的技术进行渲染
注意使用true type是浏览器和操作系统上的可选设置.如果您关闭它或具有非常低分辨率的设备,上面的缩放文本将看起来相同(底部图像中没有彩色像素)
像素和子像素
当您仔细观察LCD显示器时,您会看到每个像素由连续排列的3个子像素组成,每个像素分别用于红色,绿色和蓝色.要设置像素,请为每个颜色通道提供RGB强度,并设置相应的RGB子像素.我们通常认为红色是第一个,最后是蓝色,但实际情况是,只要它们彼此接近就会得到相同的结果并不重要.
当您停止考虑颜色和几乎可控制的图像元素时,您的设备的水平分辨率会增加三倍.由于大多数文本都是单色的,因此您不必过于担心RGB子像素的对齐,您可以将文本渲染到子像素而不是整个像素,从而获得高质量的文本.子像素是如此之小,大多数人都没有注意到轻微的颜色扭曲,这种好处值得稍微肮脏的外观.
为什么没有真正的画布类型
使用子像素时,您需要完全控制每个像素,包括alpha值.对于显示驱动程序alpha应用于像素的所有子像素,您不能在alpha为0.2时为蓝色,在alpha 0.7为相同像素时为红色.但是如果你知道每个子像素下的子像素值是什么,你可以进行alpha计算,而不是让硬件去做.这可以让你在亚像素级别进行algha控制.
不幸的是(没有... 99.99%的情况幸运)画布允许透明度,但你无法知道画布下的子像素在做什么,它们可以是任何颜色,因此你不能进行alpha计算需要有效地使用子像素.
本土亚像素文本.
但是你不必拥有透明画布,如果你使所有像素都不透明(alpha = 1.0),你就会重新获得亚像素alpha控制.
以下函数使用子像素绘制画布文本.它不是很快,但它确实获得了更好的文本质量.
它的工作原理是将文本渲染为正常宽度的3倍.然后它使用额外的像素来计算子像素值,并在完成时将子像素数据放到画布上.
更新当我写这个答案时,我完全忘记了缩放设置.使用子像素需要显示物理像素大小和DOM像素大小之间的预设匹配.如果放大或缩小,则不会如此,因此定位子像素变得更加困难.
我已更新演示以尝试检测缩放设置.由于没有标准的方法来做到这一点,我刚刚使用devicePixelRatio了FF和Chrome!== 1放大时(因为我没有视网膜装置,我只是猜测底部演示是否有效).如果您希望正确地看到演示并且您没有得到缩放警告但仍然缩放,请将缩放设置为1.
Addistionaly您可能希望将缩放设置为200%并使用底部演示,因为放大似乎缩小了DOM文本质量相当高,而画布子像素保持高品质.
顶部文本是普通的Canvas文本,center是(自制)子画面文本,底部是DOM文本
请注意,如果您有Retina Display或非常高分辨率的显示器,如果您没有看到高质量的画布文本,则应该查看此片段下方的片段.
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();
}
Run Code Online (Sandbox Code Playgroud)
对于视网膜,非常高的分辨率,或缩放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();
}
Run Code Online (Sandbox Code Playgroud)
要获得最佳效果,您需要使用webGL.这是从标准抗锯齿到子像素抗锯齿的相对简单的修改.使用webGL的标准矢量文本渲染的示例可以在WebGL PDF中找到
除了2D canvas API之外,WebGL API也很乐意将webGl渲染内容的结果复制到2D画布就像渲染图像一样简单context.drawImage(canvasWebGL,0,0)
| 归档时间: |
|
| 查看次数: |
8333 次 |
| 最近记录: |