我知道这个问题已被问过很多次,但我尝试了几乎所有我能在网上找到的东西,但无论我尝试了什么(以及任何组合),仍然无法让文本在 Canvas 中正确呈现。
对于模糊的线条和形状问题,简单地在坐标上加上+0.5px 就解决了这个问题:但是,这个解决方案似乎不适用于文本渲染。
注意:我从不使用 CSS 来设置 Canvas 的宽度和高度(只是尝试了一次以检查在 HTML 和 CSS 中设置尺寸属性是否会改变任何东西)。此外,问题似乎与浏览器无关。
我试过了:
canvas.getContext('2d', {alpha:false}) 禁用 alpha channel 这只会让我的大部分图层消失而没有解决问题在此处查看 canvas 和 html 字体渲染之间的比较:https://jsfiddle.net/balleronde/1e9a5xbf/
是否有可能让 Canvas 中的文本像 dom 元素中的文本一样呈现?任何意见或建议将不胜感激
最佳答案
仔细观察
如果放大 DOM 文本,您将看到以下内容(顶部是 Canvas ,底部是 DOM,中心希望是像素大小(不是在视网膜显示器上))
如您所见,底部文本上有彩色部分。这是因为它是使用一种称为真实类型的技术呈现的
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 个子像素组成,红色、绿色和蓝色各一个。要设置像素,您需要为每个颜色 channel 提供 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 上。
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 useddevicePixelRatiowhich for FF and Chrome are!== 1when 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 文本,中间是(自制的) Canvas 上的子像素文本,底部是 DOM 文本
请注意,如果您有 Retina 显示屏或非常高分辨率的显示屏,如果您没有看到高质量的 Canvas 文本,您应该查看下面的代码片段。
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();
}
对于视网膜、非常高分辨率或缩放 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 的标准矢量文本渲染示例。
WebGL API 将愉快地坐在 2D Canvas API 旁边,将 webGl 渲染内容的结果复制到 2D Canvas 就像渲染图像一样简单context.drawImage(canvasWebGL,0,0)
关于javascript - Canvas 文本渲染(模糊),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40066166/
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>
在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的
我有这个代码:context"Visitingtheusers#indexpage."dobefore(:each){visitusers_path}subject{page}pending('iii'){shouldhave_no_css('table#users')}pending{shouldhavecontent('Youhavereachedthispageduetoapermissionic错误')}它会导致几个待处理,例如ManagingUsersGivenapractitionerloggedin.Visitingtheusers#indexpage.#Noreason
我一直在玩一个脚本,它在Chrome中获取选定的文本并在Google中查找它,提供四个最佳选择,然后粘贴相关链接。它以不同的格式粘贴,具体取决于当前在Chrome中打开的页面-DokuWiki打开的DokuWiki格式,普通网站的HTML,我想要我的WordPress所见即所得编辑器的富文本。我尝试使用pbpaste-Preferrtf来查看没有其他样式的富文本链接在粘贴板上的样子,但它仍然输出纯文本。在文本编辑中保存文件并进行试验后,我想出了以下内容text=%q|{\rtf1{\field{\*\fldinst{HYPERLINK"URL"}}{\fldrsltTEXT}}}|te
我在一个简单的RailsAPI中有以下Controller代码:classApi::V1::AccountsControllerehead:not_foundendendend问题在于,生成的json具有以下格式:{id:2,name:'Simpleaccount',cash_flows:[{id:1,amount:34.3,description:'simpledescription'},{id:2,amount:1.12,description:'otherdescription'}]}我需要我生成的json是camelCase('cashFlows'而不是'cash_flows'
我使用“newapp_name”创建了一个新的Rails应用程序,我正在尝试编辑.gitignore文件,但在我的应用程序文件夹中找不到它。我在哪里可以找到它?我安装了Git。 最佳答案 .gitignore位于项目的root中,而不是app子目录中。首先打开终端并进入您的目录。您需要使用ls-a来显示stash文件。然后使用打开.gitignore 关于ruby-on-rails-尝试打开.gitignore以在文本编辑器中对其进行编辑,但在OSXMountainLion上找不到文件位
我想获取任意的ASCII文本字符串,例如“Helloworld”,并将其压缩为字符数较少(尽可能少)的版本,但要采用可以解压缩的方式。压缩版本应仅由ascii字符组成。有没有一种方法可以做到这一点,尤其是在Ruby中? 最佳答案 如果知道只会使用ASCII字符,那就是每个字节的低7位。通过位操作,您可以将每8个字节混合成7个字节(节省12.5%)。如果您可以将其放入更小的范围(仅限64个有效字符),则可以删除另一个字节。但是,因为您希望压缩形式也只包含ASCII字符,所以会丢失一个字节-除非您的输入可以限制为64个字符(例如,有损压