缩放时JS画布白线

时间:2019-01-27 17:29:13

标签: javascript html5-canvas

使用JavaScript,我正在html 5画布上显示一个数组。程序对数组中的每个值使用c.fillRect()。在使用c.scale()进行缩放之前,一切看起来都很正常。缩放后,在正方形之间可见白线。我知道它们是白色的,因为那是背景的颜色(当背景改变时,它们的颜色也会改变)。

由于正方形相隔5个单位,因此我尝试将其宽度设置为5.5,而不是5;仅当放大足够远时才删除白线,而缩小时仍去除白线。

这是我的代码(删除了不必要的部分):

function loop()
{
    c.resetTransform();

    c.fillStyle = "white";
    c.fillRect(0, 0, c.canvas.width, c.canvas.height);

    c.scale(scale, scale);
    c.translate(xViewportOffset, yViewportOffset);

    ...

    for(var x = 0; x < array.length; x++)
    {
        for(var y = 0; y < array[x].length; y++)
        {
            ...

            c.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')';
            c.fillRect(0 + x * 5, 200 + y * 5, 5, 5);
        }
    }

    ...
}

不缩放:

no scaling image

放大:

zoomed in image

缩小:

zoomed out image

(图案根据缩放程度而变化)

感谢您的帮助,如果需要其他任何信息,请告诉我。

更新

我正在使用Google Chrome

版本71.0.3578.98(正式版本)(64位)

1 个答案:

答案 0 :(得分:1)

这可能是因为您使用非整数值来设置上下文的缩放和/或平移。

这样做,您的 rects 不再位于像素边界上,而是位于浮动值上。

让我们举一个简单的例子:

两个像素,一个位于坐标(x,y)(11,10),另一个位于坐标(12,10)。
在默认比例下,两个像素都应该是相邻像素。
现在,如果我们应用1.3的比例,则第一个正方形的实际 pixel-coords 将为(14.3,13),第二个正方形的真实 pixel-coords 为(15.6,13 )。
这些座标都不能容纳单个像素,因此浏览器将应用抗锯齿功能,其中包括使用背景颜色使颜色平滑以给较小的像素留下印象。这就是使您成为网格的原因。

const ctx = small.getContext('2d');

ctx.scale(1.3, 1.3);

ctx.fillRect(2,10,10,10);

ctx.fillRect(12,10,10,10);


const mag = magnifier.getContext('2d');
mag.scale(10,10);
mag.imageSmoothingEnabled = false;
mag.drawImage(small, 0,-10);
/* it is actually transparent, not just more white */
body:hover{background:yellow}
<canvas id="small" width="50" height="50"></canvas><br>
<canvas id="magnifier" width="300" height="300"></canvas>

为避免这种情况,有几种解决方案,它们全都取决于您的工作。

对于您来说,似乎可以通过ImageData的工作来赢得很多,这将使您可以替换所有这些fillRect调用,从而简化和更快地处理像素。
通过使用较小的ImageData(矩阵大小),您可以将每个 rect 替换为单个像素。然后,您只需要将此矩阵放在画布上,并在禁用imageSmootingEnabled标志后以正确的比例在画布上重新绘制画布即可,这使我们可以为drawImage CanvasPatterns禁用抗锯齿

// the original matrix will be 20x20 squares
const width = 20;
const height = 20;
const ctx = canvas.getContext('2d');
// create an ImageData the size of our matrix
const img = ctx.createImageData(width, height);
// wrap it inside an Uint32Array so that we can work on it faster
const pixels = new Uint32Array(img.data.buffer);
// we could have worked directly with the Uint8 version
// but our loop would have needed to iterate 4 pixels every time

// just to draw a radial-gradient
const rad = width / 2;
// iterate over every pixels
for(let x=0; x<width; x++) {
  for(let y=0; y<height; y++) {
    // make a radial-gradient
    const dist = Math.min(Math.hypot(rad - x, rad - y), rad);
    const color = 0xFF * ((rad - dist) / rad) + 0xFF000000;
    pixels[(y * width) + x] = color;
  }
}
// here we are still at 50x50 pixels
ctx.putImageData(img, 0, 0);
// in case we had transparency, this composite mode will ensure 
// that only what we draw after is kept on the canvas
ctx.globalCompositeOperation = "copy";
// remove anti-aliasing for drawImage
ctx.imageSmoothingEnabled = false;
// make it bigger
ctx.scale(30,30);
// draw the canvas over itself
ctx.drawImage(canvas, 0,0);

// In case we draw again, reset all to defaults
ctx.setTransform(1,0,0,1,0,0);
ctx.globalCompositeOperation = "source-over";
body:hover{background:yellow}
<canvas id="canvas" width="600" height="600"></canvas>