使用drawImage()时,Canvas在浏览器中的像素网格不一致

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用drawImage()时,Canvas在浏览器中的像素网格不一致相关的知识,希望对你有一定的参考价值。

我认识到Canvas drawImage is inexplicably offset by 1 Pixel是一个非常相似的问题,但在我遇到这个问题之前,我已经应用了该问题答案中给出的建议。

我正在为基于html5的游戏实现一个精灵表系统。单个框架的定义很简单:

frame = new AnimationFrame(img, x, y, w, h);

在AnimationFrame构造函数中,所有数字参数都被截断为整数。

然后,当我在画布上绘制它时,我使用的也应该是简单的代码:

context.drawImage(frame.img,
  frame.x,
  frame.y,
  frame.w,
  frame.h,
  this.position.x | 0,
  this.position.y | 0,
  frame.w,
  frame.h,
);

不幸的是,我在不同的浏览器中得到了不同

在Mac上的Chrome和Mac上的Firefox中,以这种方式切割精灵表会导致单个帧偏移半个像素,导致角色头部的上边缘被拉得太薄而下一个精灵的顶部会下降到偷看进入这个底部。

我可以用frame.x - 0.5frame.y - 0.5解决这个问题,但如果我这样做,那么我在Mac和ios上的Safari以及Windows上的Firefox中遇到了相反的问题。

我并不特别想要做一个浏览器检测来决定如何轻推坐标系,所以我正在寻找一种方法的建议:(1)强制各种浏览器以相同的方式运行,或者(2)检测页面加载时的问题与测试,所以我可以将像素网格偏移存储在变量中。

注意:我想要一个粗糙的像素美学,所以我的画布缩放了2倍。这样可以正常工作而不会模糊,但它使半像素问题更清晰可见。没有缩放,精灵的边缘仍然会变形,所以这不是问题。

答案

我无法告诉IE,但至少对于Safari来说,当转换矩阵转换设置为n.5时,这听起来像是最近邻算法中的一个错误。

onload = function() {

  var ctx = document.querySelector('canvas').getContext('2d');
  ctx.imageSmoothingEnabled = false;
  var img = document.querySelector('#hero');

  ctx.setTransform(2, 0, 0, 2, 99.49, 99.49);
  ctx.drawImage(img, 32, 32, 16, 16, 0, 0, 16, 16);

  ctx.setTransform(2, 0, 0, 2, 99.5, 99.5);
  ctx.drawImage(img, 32, 32, 16, 16, 16, 0, 16, 16);

  ctx.setTransform(2, 0, 0, 2, 99.51, 99.51);
  ctx.drawImage(img, 32, 32, 16, 16, 32, 0, 16, 16);

};
<img src='http://xmpps.greenmaw.com/~coda/html52d/hero.png' id='hero' style='display:none' />
<canvas width='200' height='200'></canvas>
另一答案

Sprites can not share edges!

你所看到的是正常的。不仅是浏览器会产生不同的结果,而且不同的硬件和设备设置也会在不同程度上显示这个问题。

问题在于硬件,即使将所有渲染值设置为整数,这些工件也可能出现。

Why is this happening?

原因是因为你正在尝试在同一条线的两侧绘图。 GPU采样图像的方式意味着总会出现一点点错误。你永远不能摆脱它。如果你在y = 16的位置绘制,一点像素15就会流入。如果你使用的是最近的邻居,不管有多小都等于整个显示像素。

图像显示原始精灵表。顶部精灵的底部是下一个精灵精灵开始的地方。它们共享红线代表的边界。共享边界总会流血。

enter image description here

这不是浏览器错误,它是硬件中固有的。您无法通过半偏移修复它。只有一种方法可以解决这个问题。

简单解决方案

解决方案非常简单。没有两个精灵可以共享边缘,连续精灵之间需要1像素透明空间。

图像显示左侧共享边缘上的精灵并在整个线条上流血。右边的精灵用1像素透明空间固定。

enter image description here

例如

如果你有精灵16,16然后你正在使用的坐标

// Format x,y,w,h
0,0,16,16    // right edge
16,0,16,16   // touches left edge. BAD!
... 
// next row
0,16,16,16   // Top touch sprite above bottom
16,16,16,16  // Left edge touches right edge. BAD!

在所有之间添加单个像素,它们仍然可以触摸图像边缘

0,0,16,16    // spr 0
17,0,16,16   // spr 1  No shared edge between them
... 
// next row
0,17,16,16   // spr 8  no shared edge above
17,17,16,16  // spr 9

在渲染函数中,源坐标应始终被覆盖。但是,变换和目标坐标可以是浮点数。

ctx.drawImage(image,int,int,int,int,float,float,float,float);

下一张图片是正确间隔的精灵表。精灵仍然是16乘16但它们间隔17个像素,因此没有两个精灵共享边界。

enter image description here

按索引从左上到右绘制精灵,每行向下绘制一个精灵。

var posx,posy; // location of sprite on display canvas
var sprCols = 6; // number of columns
var sprWidth = 16; 
var sprHeight = 16; 
var sprIndex = ?  // index of sprite 0 top left
var x = (sprIndex % sprCols) * (sprWidth + 1);
var y = (sprIndex / sprCols | 0) * (sprHeight + 1);
ctx.drawImage(spriteSheet, x, y, sprWidth, sprHeight, posx, posy, sprWidth, sprHeight);

回到游戏创作。

现在你可以忘记那个疯狂的半像素的东西,它只是坚果,因为它不起作用。这就是它如何完成的,以及自1990年代中期第一款GPU上市以来一直如此。

注意所有来自OP的fiddle的图像

以上是关于使用drawImage()时,Canvas在浏览器中的像素网格不一致的主要内容,如果未能解决你的问题,请参考以下文章

canvas使用context.drawImage时图片不在画布上展示的问题

画布:drawImage 方法失败且没有错误

为啥HTML5canvas中drawImage()第一次加载只出现最后一个图,刷新就出现问题?

Canvas:使用drawImage捕获流仅捕获流图像的左上角[重复]

Canvas 中drawImage 绘制不出图片

来自视频的 HTML5 Canvas drawImage 在第一次绘制时未显示