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

标签 javascript html canvas html5-canvas

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

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

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

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

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

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 中,以这种方式切片 sprite 表会导致各个帧偏移半个像素,导致 Angular 色头部的顶部边缘绘制得太细,而下一个 sprite 的顶部向下窥视这个底部。

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

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

注意:我想要一个厚实的像素美学,所以我的 Canvas 缩放了 2 倍。这在没有模糊的情况下工作正常,但它使半像素问题更清晰可见。没有缩放, Sprite 的边缘仍然会变形,所以这不是问题。

最佳答案

Sprite 不能共享边!

您所看到的是正常的。不仅是浏览器会给出不同的结果,不同的硬件和设备设置也会不同程度地表现出这个问题。

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

为什么会这样?

原因是因为您正在尝试在同一条线的两侧绘制。 GPU 采样图像的方式意味着总会有一点点错误。你永远无法摆脱它。如果您在位置 y = 16 处绘制,则会渗入一点点像素 15。如果您使用的是最近邻,一点点无论多小都相当于整个显示像素。

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

enter image description here

这不是浏览器错误,它是硬件固有的。你不能用半偏移来修复它。只有一种方法可以解决问题。

简单的解决方案

解决方法很简单。没有两个 Sprite 可以共享一条边,连续的 Sprite 之间需要一个 1 像素的透明空间。

图像显示左侧共享边缘上的 Sprite 并流过线条。右边的 Sprite 固定有 1 像素的透明空间。

enter image description here

例如

如果你有 16 个 Sprite ,那么 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

在渲染函数中,源坐标应始终为 floored。但是转换和目标坐标可以是 float 。

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

下一张图片是正确间隔的 Sprite 表。 Sprite 仍然是 16 x 16,但它们之间的间距为 17 像素,因此没有两个 Sprite 共用一个边界。

enter image description here

从左上到右按索引绘制 Sprite ,向下每一行都相同。

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

关于javascript - 使用 drawImage() 时,Canvas 在浏览器中的像素网格不一致,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48998301/

相关文章:

javascript - 从 Firefox 扩展打开当前选项卡/窗口中的 URL

javascript - 在 Canvas 元素上旋转 Sprite

jquery - 设置 Canvas 元素的尺寸

html - 调整窗口大小时如何阻止div相互移动?

javascript - 将 HTML Canvas 保存到图像将无法正常工作

Android:在 Canvas 上为单个路径的 alpha 设置动画

javascript - 如何根据对象的多维数组中的id获取对象?

javascript - 使用 G+ 登录的 Chrome 扩展程序 - redirect_uri_mismatch

javascript - 谷歌地图多个标记简单过滤

固定 div 和灵活 div 的 HTML CSS 样式