Why there's white line appears in JS canvas between two shape?
I'm making a game with JS / TS (I'm using MacBook Pro), with HTML5 canvas, and there's a unexpected white line appear between two shapes in safari browser:
but I run exactly same code in chrome, everything is fine:
So why this is happened? And how can I fix it?
code I'm using to render
CONTEXT.drawImage(
CACHES.get(this.materialURL),
(this.rect.x - camera.location.x) * GRID_W,
(this.rect.y - camera.location.y) * GRID_H,
GRID_W,
GRID_H,
);
There are many reasons this can happen. Most are the result of rounding errors. Sometimes the error is in JavaScript, other times it occurs in the rendering.
There are subtle differences in the JS engines (resulting from hardware, OS, driver and or engine implementations) that can result in rendering artifact that differ across devices.
There are major differences in rendering implementations even on the same browser, same OS, and using the same hardware, depending on setup (flags).
Where your artifacts are coming from I can only guess at without a lot more information. Even how you captured the example images can change the solution.
Try using nearest pixel lookup by setting 2D context smoothing off
ctx.imageSmoothingEnabled = false;
To turn back on use
ctx.imageSmoothingEnabled = true;
Use software rendering (CPU) by setting the willReadFrequently
flag when getting the context.
const ctx = canvas.getContext("2d", {willReadFrequently: true});
Note this can slow things down a lot
Turn off canvas alpha (to stop BG appearing at seams) using context option alpha
const ctx = canvas.getContext("2d", {alpha: false});
Ensure that the source image resolution matches the render size.
In other words does
const img = CACHES.get(this.materialURL);
const isSameRes = img.width === GRID_W && img.height === GRID_H;
isSameRes
should equal true
?
Note use naturalWidth
and naturalHeight
if img
is an instance of Image
Extend the source image by 1 px on each edge copying the edge pixels as shown in next image. This will prevent transparent edge pixels bleeding into rendering result.
Then render the inner original image as shown below
const img = CACHES.get(this.materialURL);
ctx.drawImage(
img,
1, 1, img.width - 2, img.height - 2,
(this.rect.x - camera.location.x) * GRID_W,
(this.rect.y - camera.location.y) * GRID_H,
GRID_W,
GRID_H,
);
Note this will add in tiny bit of overhead.
Ensure integer coordinates by flooring coordinates and forcing constants to be integers.
// When defining GRID_W and GRID_H (assuming positive integer values)
// Force internal type to int32 by using bitwise operation on values
// Note this may not do anything
const GRID_W = 32 | 0;
const GRID_H = 32 | 0;
// Render using floored coordinates.
ctx.drawImage(
CACHES.get(this.materialURL),
Math.floor((this.rect.x - camera.location.x) * GRID_W),
Math.floor((this.rect.y - camera.location.y) * GRID_H),
GRID_W,
GRID_H,
);
There are many more options but without the needed information I would be wasting your time.