Canvas has inconsistent pixel grid across browsers when using drawImage()

前端 未结 2 452
栀梦
栀梦 2021-01-21 06:55

I recognize that Canvas drawImage is inexplicably offset by 1 Pixel is a very similar issue, but I was already applying the advice given in that question\'s answer before I

相关标签:
2条回答
  • 2021-01-21 07:11

    Sprites can not share edges!

    What you are seeing is normal. It is not only the browsers that will give different results, but different hardware and device settings will also show this problem to different degrees.

    The problem is in the hardware and these artifacts may appear even when you set all your render values to integers.

    Why is this happening?

    The reason is because you are trying drawing on two side of the same line. The way GPU's samples images means that there will always be a little bit of error. You can never get rid of it. If you draw at location y = 16 a little bit of pixel 15 will bleed in. If you are using nearest neighbor, a little bit no matter how small equate to a whole display pixel.

    The image shows your original sprite sheet. The top sprite's bottom is where the next sprite sprite starts. They share the boundary represented by the red line. A shared boundary will always bleed across.

    This is not a browser bug, it is inherent in the hardware. You can not fix it with half offsets. There is only one way to solve the problem.

    Simple solution

    The solution is very simple. No two sprites may share an edge, you need a 1 pixel transparent space between consecutive sprites.

    Image shows sprites on left share edge and bleed across the line. Sprites on right fixed with 1 pixel transparent space.

    Eg

    If you have sprite 16, by 16 then the coords you are using

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

    Add a single pixel between all, they can still touch the image edge

    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
    

    In the render function the source coordinates should always be floored. However the transform, and the destination coordinates can be floats.

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

    The next image is your sprite sheet correctly spaced. Sprites are still 16 by 16 but they are spaced 17 pixels apart so no two sprites share a boundary.

    To draw a sprite by index from top left to right and same for each row down.

    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);
    

    Back to game creation.

    Now you can forget that crazy half pixel stuff, its just nuts, as it does not work. This is how how its done, and its been done this way since the first GPU's hit the market in the mid 1990's.

    Note All images sourced from OP's fiddle

    0 讨论(0)
  • 2021-01-21 07:14

    I can't tell for IE, but at least for Safari, this sounds like a bug in their nearest-neighbor algorithm when the transformation matrix translate is set to exactly 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>

    Result in Safari 11.0.3

    You may want to let them know about it from their bug-tracker.

    The workaround would be to make sure that you never lay on floating coordinates, even in your transformation matrix.

    0 讨论(0)
提交回复
热议问题