【问题标题】:Canvas image data on retina display (MPB)视网膜显示器 (MPB) 上的画布图像数据
【发布时间】:2017-09-01 16:35:48
【问题描述】:

我已阅读有关视网膜显示器上画布模糊问题的多项建议(例如,使用 window.devicePixelRatio 方法;hereherehere)但我无法应用针对我的具体问题提出了解决方案。下面的脚本首先创建一个带有一些随机图像数据(看起来很模糊)的画布,然后将图像导出到 SVG 元素并重新缩放它(当然仍然是模糊的)。我在 2016 年末使用带有触摸栏和 safari 的 MBP。关于如何避免模糊和实现清晰边缘的任何建议?请记住,初始 imageData 应具有固定的宽度和高度。

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://d3js.org/d3.v4.min.js"></script>

<body></body>

<script type="text/javascript">

    var width = 100;
    var height = 100;

    var canvas = d3.select("body").append("canvas");
    context = canvas.node().getContext("2d"),

    canvas
    .attr("width", width)
    .attr("height", height)
    .style("width", width + "px")
    .style("height", height + "px")

    //this is the part that should normally take care of blurriness 
    if (window.devicePixelRatio > 1) {
      var devicePixelRatio = window.devicePixelRatio || 1;
      var backingStoreRatio = context.webkitBackingStorePixelRatio ||
      context.backingStorePixelRatio || 1;
      var ratio = devicePixelRatio / backingStoreRatio;
      canvas
      .attr('width', width * ratio)
      .attr('height', height * ratio)
      .style('width', width + 'px')
      .style('height', height + 'px');
      context.scale(ratio, ratio);
    }

    var imageData = context.createImageData(width, height);

    for (var i = 0, l = 0; i<height; ++i) {
        for (j = 0; j<width; ++j, l += 4) {
            imageData.data[l+0] = Math.round( Math.random() * 255);
            imageData.data[l+1] = Math.round( Math.random() * 255);
            imageData.data[l+2] = Math.round( Math.random() * 255);
            imageData.data[l+3] = Math.round( Math.random() * 255);
        }

    }

    context.putImageData(imageData, 0, 0);
    var ImageD = canvas.node().toDataURL("img/png");

    var svg = d3.select('body').append('svg').attr('width', width*5).attr('height', height*5);
    svg.append("svg:image").datum(ImageD).attr("xlink:href", function(d) {return d})
                    .attr("height", height*5).attr("width", width*5)

</script>

【问题讨论】:

  • 为什么您无法将建议的解决方案应用于您的具体问题?
  • 好吧,上面的脚本实现了这些解决方案没有成功
  • Retina 将 CSS 像素设置为 2 个物理像素。画布分辨率设置为 CSS 像素。要修复将画布分辨率设置为画布大小的 2 倍。通过const bounds = element.getBoundingClientRect(); 获取大小,然后将画布大小设置为canvas.width = bounds.width * 2canvas.height = bounds.height * 2

标签: javascript d3.js canvas svg retina-display


【解决方案1】:

我终于找到了解决办法。我使用以下组合:window.devicePixelRatio 获取视网膜像素比,屏幕外画布取自 here,然后放大取自 here 的上下文

<!DOCTYPE html>
<meta charset="utf-8">

<script src="https://d3js.org/d3.v4.min.js"></script>

<body></body>

<script type="text/javascript">

    const width = 20;
    const height = 20;
    const scale = 10; // the higher the number the crisper the custom image

    var canvas = d3.select("body").append("canvas");
    context = canvas.node().getContext("2d");

    const ratio = window.devicePixelRatio || 1;
    canvas.attr('width', width * ratio * scale)
        .attr('height', height * ratio * scale)
        .style('width', width * scale + 'px')
        .style('height', height * scale + 'px');

    var imageData = context.createImageData(width, height);

    for (var i = 0, l = 0; i<height; ++i) {
        for (j = 0; j<width; ++j, l += 4) {
            imageData.data[l+0] = Math.round( Math.random() * 255);
            imageData.data[l+1] = Math.round( Math.random() * 255);
            imageData.data[l+2] = Math.round( Math.random() * 255);
            imageData.data[l+3] = Math.round( Math.random() * 255);
        }
    }

    const offCtx = canvas.node().cloneNode().getContext('2d'); // create an off screen canvas
    offCtx.putImageData(imageData, 0,0);
    context.scale(ratio * scale, ratio * scale);
    context.mozImageSmoothingEnabled = false;
    context.imageSmoothingEnabled = false;
    context.drawImage(offCtx.canvas, 0,0);

    //export image
    var ImageD = canvas.node().toDataURL("img/png");
    //load image
    d3.select('body').append('svg').attr("height", 500).attr("width", 500).append("svg:image").datum(ImageD).attr("xlink:href", function(d) {return d})
                .attr("height", 500).attr("width", 500);

</script>

【讨论】:

    【解决方案2】:

    图像数据不支持上下文。

    如果您使用上下文进行渲染,您的代码将可以工作,但您将像素直接写入图像缓冲区。这不受 2D 上下文转换的影响,因此您的代码不会按比例放大。

    在你的代码中

    context.scale(ratio, ratio);
    

    按比例放大画布渲染不适用于图像数据。

    简单修复

    如果您知道设备是 Retina,则可以进行简单的修复。它将画布分辨率加倍,然后设置随机像素。为了与您的原始代码保持一致,我将 2 x 2 像素设置为相同的随机值。模糊将消失,但随机模式保持不变。

    const width = 100;
    const height = 100;
    const w = width;  // because I hate cluttered code
    const h = height;
    
    const canvas = document.createElement("canvas");
    document.body.appendChild(canvas);
    const ctx = canvas.getContext("2d");
    
    canvas.width = w * 2;
    canvas.height = h * 2;
    canvas.style.width = w + "px";
    canvas.style.height = h + "px";
    
    const imageData = ctx.createImageData(w * 2, h * 2);
    // get 32bit view of data
    const b32 = new Uint32Array(imageData.data.buffer);
    // this is the part that you need to change as the canvas resolution is double
    
    for (let i = 0, l = 0; i< h; i ++) {
        for (let j = 0; j < w; j ++) {
            const idx = i * w* 2 + j * 2;
            b32[idx + w + 1] = b32[idx + w] = b32[idx + 1] = b32[idx] = (Math.random() * 0xFFFFFFFF) | 0;
        }
    }
    
    ctx.putImageData(imageData, 0, 0);
    const ImageD = canvas.toDataURL("img/png");
    
    const svg = d3.select('body').append('svg').attr('width', width*5).attr('height', height*5);
    svg.append("svg:image").datum(ImageD).attr("xlink:href", function(d) {return d})
                    .attr("height", height*5).attr("width", width*5)
    

    【讨论】:

    • 上述脚本仍然会产生模糊的图像。此外,它还改变了图像尺寸的比例。
    • @spyrostheodoridis 对不起,我的错误应该是 ctx.createImageData(w, h); as ctx.createImageData(w * 2, h * 2); 虽然这并不能解释这个方面。至于VGA,你将图像添加为5*宽和5*高,如果你不匹配物理像素分辨率,它总是会模糊
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多