【问题标题】:How draw in high resolution to canvas on Chrome? And why if devicePixelRatio === webkitBackingStorePixelRatio does scaling to 2x improve resolution?如何在 Chrome 上以高分辨率绘制画布?为什么如果 devicePixelRatio === webkitBackingStorePixelRatio 缩放到 2 倍会提高分辨率?
【发布时间】:2013-10-09 04:41:12
【问题描述】:

我正在尝试将 300dpi 的图像绘制到画布对象上,但在 Chrome 中它显示的质量很差。当我使用下面的代码时,它并没有改善,但那是因为devicePixelRatiobackingStoreRatio 相同(两者都是1)。

然后我尝试强制更改一些比率并发现以下内容:

  • 如果我将ratio 更改为2 并强制运行缩放代码,那么它将以更好的分辨率绘制到画布上。
  • 如果我将ratio 更改为大于2 的任何值(例如3456 等),那么它的分辨率较差

这一切都是在台式计算机上完成的。

如何确保画布以高分辨率绘制?

(代码来自:http://www.html5rocks.com/en/tutorials/canvas/hidpi/

/**
* Writes an image into a canvas taking into
* account the backing store pixel ratio and
* the device pixel ratio.
*
* @author Paul Lewis
* @param {Object} opts The params for drawing an image to the canvas
*/
function drawImage(opts) {

    if(!opts.canvas) {
        throw("A canvas is required");
    }
    if(!opts.image) {
        throw("Image is required");
    }

    // get the canvas and context
    var canvas = opts.canvas,
    context = canvas.getContext('2d'),
    image = opts.image,

    // now default all the dimension info
    srcx = opts.srcx || 0,
    srcy = opts.srcy || 0,
    srcw = opts.srcw || image.naturalWidth,
    srch = opts.srch || image.naturalHeight,
    desx = opts.desx || srcx,
    desy = opts.desy || srcy,
    desw = opts.desw || srcw,
    desh = opts.desh || srch,
    auto = opts.auto,

    // finally query the various pixel ratios
    devicePixelRatio = window.devicePixelRatio || 1,
    backingStoreRatio = context.webkitBackingStorePixelRatio ||
    context.mozBackingStorePixelRatio ||
    context.msBackingStorePixelRatio ||
    context.oBackingStorePixelRatio ||
    context.backingStorePixelRatio || 1,    
    ratio = devicePixelRatio / backingStoreRatio;

    // ensure we have a value set for auto.
    // If auto is set to false then we
    // will simply not upscale the canvas
    // and the default behaviour will be maintained
    if (typeof auto === 'undefined') {
        auto = true;
    }

    // upscale the canvas if the two ratios don't match
    if (auto && devicePixelRatio !== backingStoreRatio) {

        var oldWidth = canvas.width;
        var oldHeight = canvas.height;

        canvas.width = oldWidth * ratio;
        canvas.height = oldHeight * ratio;

        canvas.style.width = oldWidth + 'px';
        canvas.style.height = oldHeight + 'px';

        // now scale the context to counter
        // the fact that we've manually scaled
        // our canvas element
        context.scale(ratio, ratio);

    }

    context.drawImage(pic, srcx, srcy, srcw, srch, desx, desy, desw, desh);
}

仅进行以下更改会生成高分辨率画布图像(为什么?):

    //WE FORCE RATIO TO BE 2
    ratio = 2;

    //WE FORCE IT TO UPSCALE (event though they're equal)
    if (auto && devicePixelRatio === backingStoreRatio) {

如果我们把上面改成3的比例,就不再是高分辨率了!

编辑:另外一项观察 - 即使使用 2x 比率,虽然它的分辨率明显更好,但仍不如在 img 标签中显示图像那么清晰)

【问题讨论】:

  • @Aardvark 正如我在帖子中提到的那样,我已经尝试了该文章(HTML5Rocks)中的代码,这是该帖子所引用的,但它不起作用,因为它始终显示比率为@987654338 @,什么都不做。
  • 你搞砸了 imageSmoothingEnabled 吗?
  • @Aardvark 是的,没有一个能提供更好的分辨率/清晰度/清晰度。画布无法完美再现图像。这是一种耻辱,没有任何意义。如果img 标签可以以清晰的分辨率显示图像,那么相同大小(并且具有 2 倍比例)的画布也应该能够做到。但它的锐度只有 85% 左右。

标签: javascript html google-chrome canvas html5-canvas


【解决方案1】:

从问题链接的 HTML5Rocks 文章使事情变得比他们需要的更困难,但它犯了与我见过的其他资源相同的基本错误(1234) .这些参考资料对这个公式给出了一些变化:

var rect = canvas.getBoundingClientRect();
canvas.width = Math.round (devicePixelRatio * rect.width); // WRONG!

公式是错误的。更好的公式是

var rect = canvas.getBoundingClientRect();
canvas.width = Math.round (devicePixelRatio * rect.right)
             - Math.round (devicePixelRatio * rect.left);

关键是,通过 devicePixelRatio 缩放宽度或高度(,两个位置的差异)是没有意义的。您应该只缩放绝对位置。我找不到这个确切点的参考,但我认为一旦你得到它就很明显了。

提议。

无法根据其 CSS 宽度和高度(以设备无关的像素为单位)计算矩形的物理宽度和高度(以设备像素为单位)。

证明。

假设您有两个元素,其边界矩形在与设备无关的像素中是

{ left:   0, top:  10, right:   8, bottom:  20, width:   8, height:  10 },
{ left:   1, top:  20, right:   9, bottom:  30, width:   8, height:  10 }.

现在假设 devicePixelRatio 为 1.4,元素将覆盖这些设备像素矩形:

{ left:   0, top:  14, right:  11, bottom:  28, width:  11, height:  14 },
{ left:   1, top:  28, right:  13, bottom:  42, width:  12, height:  14 },

其中 left、top、right 和 bottom 已乘以 devicePixelRatio 并四舍五入为最接近的整数(使用 Math.round())。

您会注意到,这两个矩形在设备无关像素中具有相同的宽度,但在设备像素中具有不同的宽度。 ▯

测试。

这是一个用于测试的代码示例。在浏览器中加载它,然后用鼠标放大和缩小。最后一张画布应该总是有清晰的线条。 其他三个在某些分辨率下会变得模糊。

在桌面 Firefox、IE、Edge、Chrome 和 Android Chrome 和 Firefox 上测试。 (注意,这在 JSfiddle 上不起作用,因为 getBoundingClientRect 在那里返回不正确的值。)

<!DOCTYPE html>
<html>
  <head>
    <script>
      function resize() {
        var canvases = document.getElementsByTagName("canvas");
        var i, j;
        for (i = 0; i != canvases.length; ++ i) {
          var canvas = canvases[i];
          var method = canvas.getAttribute("method");
          var dipRect = canvas.getBoundingClientRect();
          var context = canvas.getContext("2d");
          switch (method) {
            case "0":
              // Incorrect:
              canvas.width = devicePixelRatio * dipRect.width;
              canvas.height = devicePixelRatio * dipRect.height;
              break;

            case "1":
              // Incorrect:
              canvas.width = Math.round(devicePixelRatio * dipRect.width);
              canvas.height = Math.round(devicePixelRatio * dipRect.height);
              break;

            case "2":
              // Incorrect:
              canvas.width = Math.floor(devicePixelRatio * dipRect.width);
              canvas.height = Math.floor(devicePixelRatio * dipRect.height);
              break;

            case "3":
              // Correct:
              canvas.width = Math.round(devicePixelRatio * dipRect.right)
                - Math.round(devicePixelRatio * dipRect.left);
              canvas.height = Math.round(devicePixelRatio * dipRect.bottom)
                - Math.round(devicePixelRatio * dipRect.top);
              break;
          }
          console.log("method " + method
            + ", devicePixelRatio " + devicePixelRatio
            + ", client rect (DI px) (" + dipRect.left + ", " + dipRect.top + ")"
            + ", " + dipRect.width + " x " + dipRect.height
            + ", canvas width, height (logical px) " + canvas.width + ", " + canvas.height);

          context.clearRect(0, 0, canvas.width, canvas.height);
          context.fillStyle = "cyan";
          context.fillRect(0, 0, canvas.width, canvas.height);
          context.fillStyle = "black";
          for (j = 0; j != Math.floor (canvas.width / 2); ++ j) {
            context.fillRect(2 * j, 0, 1, canvas.height);
          }
        }
      };
      addEventListener("DOMContentLoaded", resize);
      addEventListener("resize", resize);
    </script>
  </head>
  <body>
    <canvas method="0" style="position: absolute; left: 1px; top: 10px; width: 8px; height: 10px"></canvas>
    <canvas method="1" style="position: absolute; left: 1px; top: 25px; width: 8px; height: 10px"></canvas>
    <canvas method="2" style="position: absolute; left: 1px; top: 40px; width: 8px; height: 10px"></canvas>
    <canvas method="3" style="position: absolute; left: 1px; top: 55px; width: 8px; height: 10px"></canvas>
  </body>
</html>

【讨论】:

    【解决方案2】:

    您可以使用的一个技巧是将画布宽度和高度实际设置为照片的像素高度和宽度,然后使用 css 调整图像大小。下面是一个 sudo 代码示例。

    canvasElement.width = imgWidth;
    canvasElement.height = imgHeight;
    canvasElement.getContext("2d").drawImage(ARGS);
    

    然后您可以使用宽度和高度设置或 3d 变换。

    canvasElement.style.transform = "scale3d(0.5,0.5,0)";
    

    canvasElement.style.width = newWidth;
    canvasElement.style.height = newWidth * imgHeight / imgWidth;
    

    我建议您使用 3d 转换,因为当您使用 3d 转换时,元素的图像数据会被复制到 GPU,因此您不必担心任何质量下降。

    我希望这会有所帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-25
      • 1970-01-01
      • 2016-03-04
      • 2019-03-15
      相关资源
      最近更新 更多