【问题标题】:How do I fix blurry shape edges in HTML5 canvas?如何修复 HTML5 画布中模糊的形状边缘?
【发布时间】:2017-07-24 03:09:23
【问题描述】:

我使用 canvas 元素制作了一个非常简单的矩形。但是,如果 fillRect(x, y, width, height) 中 x 和 y 的参数是 0 和 0 以外的任何值,则所有边缘在放大和/或在移动设备上看起来完全模糊。如果 x 和 y 是 0 和 0,则矩形的顶部和左侧边缘是超定义的,即使放大也是如此,而底部和右侧边缘是模糊的。我正在使用 Chrome/Firefox 的 1920x1080 屏幕以及使用 Safari 的 750x1334 移动屏幕上渲染它。

这在 100% 缩放时在桌面上不是问题,但在移动设备上看起来很糟糕。如果您在 Chrome 和 Firefox 以及 JSFiddle 上完全放大,您可以清楚地看到模糊的边缘。我没有使用 CSS 调整画布上的宽度和高度。它是使用画布属性和/或 JS 完成的。下面是我用来在浏览器上测试的 HTML。

<!DOCTYPE html>
<html>
    <head>
         <meta charset="utf-8">
         <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    </head>

    <body>
         <canvas id="gameCanvas" width="150" height="150">A game.</canvas>

         <script>
             var canvas = document.getElementById("gameCanvas");
             var ctx = canvas.getContext("2d");

             ctx.fillRect(0, 0, 100, 100);
         </script>
    </body>
</html>

编辑:我不是想画一条 1 像素的线。我也尝试过半像素值,但它使模糊的边缘变得更糟。

前两个屏幕截图来自 Safari 上的 iPhone 7 屏幕,分别为非缩放和缩放。最后一张屏幕截图是在 1920x1080 笔记本电脑屏幕上,在 Chrome 上放大。

【问题讨论】:

  • 我尝试使用半像素值进行试验,但这只会让它们变得更糟。而且我不是在画一条 1 像素的线,而是在画矩形。
  • 没有。不是同一个修复。我逐字逐句地尝试了它,虽然调整后的效果看起来好多了,但它的边缘仍然有些模糊。和我现在一样。
  • 你尝试了所有的答案?这是令人难以置信的快速编码!

标签: javascript html canvas responsive-design


【解决方案1】:

我知道出了什么问题。这是设备的device-pixel-ratio 属性。 1 以外的任何值都会导致画布内容像素化。在浏览器中调整缩放会改变device-pixel-ratio,并且某些设备具有较高的设备像素比,例如视网膜显示屏 iPhone。

您必须使用 Javascript 来解决此问题。没有其他办法。 I wrote about this in more detail on my blog, and provide some other sources as well.

您可以在下面看到最终结果。

使用原生 JavaScript 的响应式画布:

var aWrapper = document.getElementById("aWrapper");
var canvas = document.getElementById("myCanvas");

//Accesses the 2D rendering context for our canvasdfdf
var ctx = canvas.getContext("2d");

function setCanvasScalingFactor() {
   return window.devicePixelRatio || 1;
}

function resizeCanvas() {
    //Gets the devicePixelRatio
    var pixelRatio = setCanvasScalingFactor();

    //The viewport is in portrait mode, so var width should be based off viewport WIDTH
    if (window.innerHeight > window.innerWidth) {
        //Makes the canvas 100% of the viewport width
        var width = Math.round(1.0 * window.innerWidth);
    }
  //The viewport is in landscape mode, so var width should be based off viewport HEIGHT
    else {
        //Makes the canvas 100% of the viewport height
        var width = Math.round(1.0 * window.innerHeight);
    }

    //This is done in order to maintain the 1:1 aspect ratio, adjust as needed
    var height = width;

    //This will be used to downscale the canvas element when devicePixelRatio > 1
    aWrapper.style.width = width + "px";
    aWrapper.style.height = height + "px";

    canvas.width = width * pixelRatio;
    canvas.height = height * pixelRatio;
}

var cascadeFactor = 255;
var cascadeCoefficient = 1;

function draw() {
  //The number of color block columns and rows
  var columns = 5;
  var rows = 5;
  //The length of each square
  var length = Math.round(canvas.width/columns) - 2;
  
  //Increments or decrements cascadeFactor by 1, based on cascadeCoefficient
  cascadeFactor += cascadeCoefficient;

  //Makes sure the canvas is clean at the beginning of a frame
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  for (var i = columns; i >= 1; i--) {  
    for (var j = rows; j >= 1; j--) {
      //Where the color magic happens
      ctx.fillStyle = "rgba(" + (j*i*(cascadeFactor-110)) + "," + (i*cascadeFactor) + "," + (j*cascadeFactor) + "," + 0.6 + ")";
        
      ctx.fillRect((length*(i-1)) + ((i-1)*2), (length*(j-1)) + ((j-1)*2), length, length);
    }
  }
  
  if (cascadeFactor > 255 || cascadeFactor < 0) {
    //Resets the color cascade
    cascadeCoefficient = -cascadeCoefficient;
  }
  //Continuously calls draw() again until cancelled
  var aRequest = window.requestAnimationFrame(draw);
}

window.addEventListener("resize", resizeCanvas, false);

resizeCanvas();
draw();
#aWrapper {
    /*Horizontally centers the canvas*/
    margin: 0 auto;
}

#myCanvas {
    /*This eliminates inconsistent rendering across browsers, canvas is supposed to be a block-level element across all browsers anyway*/
    display: block;

    /*myCanvas will inherit its CSS width and style property values from aWrapper*/
    width: 100%;
    height: 100%;
}
asdfasdf
<div id="aWrapper">
    <!--Include some fallback content on the 0.00001% chance your user's browser doesn't support canvas -->
    <canvas id="myCanvas">Fallback content</canvas>
</div> 

【讨论】:

  • 我有非常小的字体(需要很小,因为它们在小物体上)由于放大时的模糊因素而难以辨认。指向博客的链接似乎已关闭。该代码似乎涉及调整画布的大小。画布不能设置为不随屏幕缩放,而是显示像素完美的图像质量并根据需要进行裁剪?
【解决方案2】:

还有一种使用 image-rendering 属性的 css 方式。

canvas {
  /* all four are needed to support the most browsers */
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-crisp-edges;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
/* optional: can also be applied to `img` tags */

这是放大像素艺术的理想选择。它会保留边缘并且在放大或放大时不会模糊它们。

要按预期工作,请务必在整个像素上绘制图像和形状,不要这样做:context.rect(1.5, 1.5, 2, 2)。虽然它不会模糊,但它会以不同的颜色绘制半像素。如果动态计算位置,则四舍五入到最接近的整数。

此时需要vendor-prefix 和repeated 属性来支持所有主流浏览器。 Chrome/Webkit 只支持pixelated,Firefox 只支持crisp-edges。 (2021 年中)

caniuse browser support

MDN tutorial for pixel-art

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-17
    • 1970-01-01
    • 2013-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-14
    • 2018-07-24
    相关资源
    最近更新 更多