Canvas 视网膜显示屏上的图像数据 (MPB)

Canvas image data on retina display (MPB)

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

<!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>

图像数据不是上下文感知的。

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

在你的代码行中

context.scale(ratio, ratio);

放大 canvas 渲染不适用于图像数据。

简单修复

如果您知道该设备是视网膜设备,则可以进行简单修复。它将 canvas 分辨率加倍,然后设置随机像素。为了与您的原始代码保持一致,我将 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)

我终于找到了解决办法。我使用以下组合:window.devicePixelRatio 获取视网膜像素比,屏幕外 canvas 取自 , and then scaling up the context taken from 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>