画布绘图,如线条一样,会模糊不清。

100

我有一个<div style="border:1px solid border;" />和canvas,它是使用以下代码绘制的:

context.lineWidth = 1;
context.strokeStyle = "gray";

绘图看起来相当模糊(线宽小于一会产生更糟糕的图片),并且离div边界很远。是否有可能使用Canvas获得与HTML相同的绘图质量?

var ctx = document.getElementById("canvas").getContext("2d");
ctx.lineWidth = 1;
ctx.moveTo(2, 2);
ctx.lineTo(98, 2);
ctx.lineTo(98, 98);
ctx.lineTo(2, 98);
ctx.lineTo(2, 2);
ctx.stroke();
div {
  border: 1px solid black;
  width: 100px;
  height: 100px;
}
canvas, div {background-color: #F5F5F5;}
canvas {border: 1px solid white;display: block;}
<table>
<tr><td>Line on canvas:</td><td>1px border:</td></tr>
<tr><td><canvas id="canvas" width="100" height="100"/></td><td><div>&nbsp;</div></td></tr>
</table>


为了解决画布图像模糊问题,我正在开发一个名为js2dx.com的JS框架。它由DIV马赛克组成画布图像,结果是更清晰的显示,但代价是CPU/内存。 - Gonki
这个回答解决了你的问题吗?如何修复HTML5画布中的模糊文本? - AncientSwordRage
@Gonki 这个工作做好了吗? - samuelbrody1249
1
@samuelbrody1249 不,该项目已经改变方向了。 - Gonki
16个回答

121

我发现在CSS中设置画布大小会导致我的图像以模糊的方式显示。

请尝试以下方法:

<canvas id="preview" width="640" height="260"></canvas>
根据我的帖子: HTML模糊的画布图片

1
此外,最好设置宽度和高度属性,尽管已经通过CSS设置了这些属性。 - Rutwick Gangurde
9
多么奇怪的事情。这就是你继续滚动的原因。 - Casey Dwayne
1
如果我被迫使用CSS/Javascript设置画布的高度/宽度,有什么解决方法?例如,在我的情况下,我需要画布具有响应性,因此我需要动态计算其尺寸并设置它。有什么想法吗?谢谢。 - Souvik Ghosh
你可以钩入JavaScript的resize事件处理程序,每次触发大小处理程序时重新绘制画布:-) - Ben Pretorius
1
这终于解决了我的问题!!根据我的测试,通过DOM设置CSS画布大小不会使图像变得模糊,所以如果有人想尝试设置CSS,可以尝试像这样做:canvas.style.width = '200px',这对我有效。 - Imtinan Azhar
你节省了我很多时间。谢谢!这应该被标记为已接受的答案。 - ahmethungari

77

在canvas中绘制直线时,实际上需要跨越像素。在我看来,这是API中一个奇怪的选择,但很容易使用:

不要使用这种方法:

context.moveTo(10, 0);
context.lineTo(10, 30);

请执行以下操作:

context.moveTo(10.5, 0);
context.lineTo(10.5, 30);

《深入HTML5的Canvas章节》对此有很好的讲解


难道 Y 坐标也不需要偏移半个像素吗? - user1118321
1
是的,他们很可能也应该这样做。在《深入HTML5》中,他没有费心思,因为他正在整个画布上绘制清晰的线条,所以我只是复制了他的内容,以使阅读我的答案和阅读他的解释更加一致。 - Matt Greer
18
谢谢您,这帮助我解决了一个非常烦人的问题。我稍微有所不同,我在绘制之前使用了 context.translate(0.5, 0.5);,然后在之后使用了 context.translate(-0.5, -0.5);,这样我就不必把0.5加到每个元素上了。 - Chris
2
这听起来很明显,但是这是一个容易犯的错误:确保您的浏览器视图缩放到100%(Cmd-0)。我的浏览器视图被放大了,花了我10分钟才弄清楚为什么我的线条不够清晰。傻瓜。 - Juniper Jones
1
注意:这也是OpenGL/DirectX/Metal/Vulkan的工作原理。如果你通过数学计算来理解它,就会明白为什么它是这样的。传递给画布的值是像素的边缘。如果你说fillRect(0,0,1,1),你就是在说填充从0,0像素的左上角开始,到0,0像素的右下角结束的矩形。加入当前的变换,你可能就能看出为什么它被设计成这样了。你不是在画像素,而是在画任意的矩形。线条也是一样的,它们只是围绕着线条扩展的矩形。 - gman
请参考下面的答案,关于不通过CSS设置画布的高度/宽度,以获得更好的解决方案。 - Fahad

59

更简单的解决方法是直接使用以下内容:

context = canvas.context2d;    
context.translate(0.5, 0.5);

从现在开始,你的坐标应该根据那0.5个像素进行调整。


1
@分号 你需要将画布垂直于正在绘制的线条方向进行翻译。 除非在 x.5 像素上进行绘制,否则无需翻译 fillRect。 - Super Fighting Robot
1
只是要注意,每次调用时都会进行此翻译。它不是相对于0的,而是每次将值添加到x和y上。我有一个错误,导致我的画布在对角线方向上移动,这就是原因。 - brad

35

我使用的是Retina显示屏,我找到了一个适合我的解决方案这里

小结:

首先,您需要将画布的尺寸设置为您想要的两倍,例如:

canvas = document.getElementById('myCanvas');
canvas.width = 200;
canvas.height = 200;

然后使用CSS将其设置为所需的大小:

canvas.style.width = "100px";
canvas.style.height = "100px";

最后你通过2倍缩放绘图上下文:

const dpi = window.devicePixelRatio;
canvas.getContext('2d').scale(dpi, dpi);

16
这基本上是正确的,但更好的方法是使用 window.devicePixelRatio:https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio - langpavel
1
这应该是正确的答案,只要@LittleJoe能够根据langpavel的建议将window.devicePixelRatio添加为缩放比例即可更新答案。 - parse
1
这个使用 window.devicePixelRatio 的解决方案对我很有帮助。现在在我的设备上完美清晰了。感谢 @langpavel 和 @littlejoe。 - Lukas R.
你会如何将 window.devicePixelRatio 插入到这里?像这样 var dpr = window.devicePixelRatio; canvas.getContext('2d').scale(dpr,dpr);?如果是这种情况,您是否仍然需要在 CSS 中将画布大小加倍然后减半?我正在尝试使用 window.devicePixelRatio 画一个完美的标尺,但如果在 canvas.getContext('2d').scale() 中使用它,我必须相应地重新计算一切。 - JohnRDOrazio
如果我不是将原始大小加倍,而是将其乘以window.devicePixelRatio的结果,然后在css中设置我的预期大小,最后按照我之前的评论缩放,似乎可以接近。但是,window.devicePixelRatio的值显然只能在javascript中使用,而不能在html中使用,因此这对于创建通用解决方案来说并没有什么用处... - JohnRDOrazio
这应该是2023年的被接受答案。使用这种DPI方法时,似乎不再需要将画布平移0.5像素进行翻译。 - undefined

30

Mozilla官网提供了如何在canvas中应用正确分辨率的示例代码:https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio

enter image description here

var canvas = document.getElementById('canvas');

var ctx = canvas.getContext('2d');

// Set display size (css pixels).
var size = 200;
canvas.style.width = size + "px";
canvas.style.height = size + "px";

// Set actual size in memory (scaled to account for extra pixel density).
var scale = window.devicePixelRatio; // Change to 1 on retina screens to see blurry canvas.
canvas.width = size * scale;
canvas.height = size * scale;

// Normalize coordinate system to use css pixels.
ctx.scale(scale, scale);

ctx.fillStyle = "#bada55";
ctx.fillRect(10, 10, 300, 300);
ctx.fillStyle = "#ffffff";
ctx.font = '18px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';

var x = size / 2;
var y = size / 2;

var textString = "I love MDN";
ctx.fillText(textString, x, y);
<canvas id="canvas"></canvas>


5
我花了很长时间才找到这个好的解决方案,它值得更多的点赞! - mfluehr
1
对于我的情况,我将画布设置为扩展到父容器的100%,因此无法在HTML标记中设置值。这是唯一能够消除模糊的解决方案。 - Joshlucpoll
非常棒的信息,而且工作精确! - Yan King Yin

15

由于画布的虚拟大小被缩放到其HTML元素的实际大小,因此线条变得模糊不清。为了解决这个问题,在绘制之前需要调整画布的虚拟大小:

function Draw () {
 var e, surface;
 e = document.getElementById ("surface");
 /* Begin size adjusting. */
 e.width = e.offsetWidth;
 e.height = e.offsetHeight;
 /* End size adjusting. */
 surface = e.getContext ("2d");
 surface.strokeRect (10, 10, 20, 20);
}
window.onload = Draw ()
<!DOCTYPE html>
<html>
<head>
<title>Canvas size adjusting demo</title>
</head>
<body>
<canvas id="surface"></canvas>
</body>
</html>

HTML:


非常好,甚至不需要改变任何其他东西,一切都自然而然地落在了它的位置上。 - Tsepo Nkalai

6

好的,我彻底明白了。你需要做两件事:

  1. 将任何线条放置在0.5像素上。参考这个链接,它提供了一个很好的解释:

https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors#A_lineWidth_example

  1. 画布实际上有两个高度和两个宽度。一是画布的高度和宽度,二是元素的CSS样式高度和宽度。它们需要同步。

为此,您需要计算CSS高度和宽度:

 var myCanvasEl = document.getElementById('myCanvas');
 var ctx = myCanvasEl.getContext('2d');
 myCanvasEl.style.height = myCanvasEl.height / window.devicePixelRatio + "px";
 myCanvasEl.style.width = myCanvasEl.width / window.devicePixelRatio + "px";
 

其中myCanvasEl.style.heightmyCanvasEl.style.width是元素的CSS样式高度和宽度,而myCanvasEl.heightmyCanvasEl.width是画布的高度和宽度。

旧答案(已被上述内容取代):

这是我在2020年找到的最佳解决方案。请注意,我将devicePixelRatio乘以2:

 var size = 100;
 var scale = window.devicePixelRatio*2;
 context.width = size * scale;
 cartesian_001El.style.height = cartesian_001El.height / window.devicePixelRatio + "px";
 cartesian_001El.style.height = cartesian_001El.height / window.devicePixelRatio + "px";
 context.height = size * scale;
 context.scale(scale, scale);

3

在图片缩放时(这也是我的问题),很少有人谈到的另一个问题是imageSmoothingEnabled

CanvasRenderingContext2D接口的imageSmoothingEnabled属性是Canvas API的一部分,决定是否对缩放后的图像进行平滑处理(true,默认)或不进行平滑处理(false)。获取imageSmoothingEnabled属性时,返回的是它最后设置的值。

此属性适用于使用像素艺术的游戏和其他应用程序。当放大图像时,默认的调整大小算法会模糊像素。将此属性设置为false可保留像素的清晰度。

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled

要禁用它,只需将该属性设置为false:

ctx.imageSmoothingEnabled = false;

2
canvas.width=canvas.clientWidth
canvas.height=canvas.clientHeight

这是答案。应该得到更多的赞。 - Nitsan BenHanoch
另外,我会将它挂钩到一个 window.addEventListener('resize', ...)。 - Nitsan BenHanoch

1
这是我的解决方案:为画布设置宽度和高度。
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

同时在CSS中进行设置,这样它就不会从其父元素中溢出

canvas {
  width: 100%
  height: 100%
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接