Border artifact in Game Maker

Sometimes, the borders of the sprites can show a strange artifact. This is not a problem related to Game Maker, but in general to all applications that use GPU. Here is a screenshot that shows the problem:

As you can see in the image, by scaling the sprite by 200%, an unexpected colored border appeared.

Causes

The frames of the sprites are composed of PNG images with information about transparency (alpha channel). It means that, for every pixel, we have 4 bytes of information: 3 for the color (RGB), 1 for transparency. Suppose we have a sprite with sharp edges (there are only pixels with 255 and 0 transparency). Usually, totally transparent background pixels are not visible; so, whatever color I assign to them, this will not be visible at runtime.

However, suppose we want to scale the sprite by 200%, and that the texture filter is active. Because of this, pixels having a mix between the border color and the background color will be sampled. In practice, the background color becomes visible, and this is true also if we apply rotations or set float values as coordinates.

Solutions

There are two solutions to the problem, equivalent at all (the result is absolutely equal). The first is to assign to the background pixels the same color of the border (with null alpha, obviously). Unfortunately, this solution is not good for sprites without borders, or otherwise without a precise border color.

The second solution is to use sprites with premultiplied alpha. With Game Maker, you can premultiply alpha channel in sprites editor. The result will be that, the more the pixel is transparent, the darker it will become. Totally transparent pixels will be black at all.

In order to draw correctly a sprite with premultiplied alpha, we need a blend mode alternative to the default one. The blend mode is the method that is used to determine the final color of the pixels after the blend of the texture; it allows to combine under certain rules the pixels of the texture with the screen ones. Here is the default blend mode:

blend(source, dest) = (source.rgb * source.a) + (dest.rgb * (1 – source.a))

We want the following blend mode instead:

blend(source, dest) = (source.rgb) + (dest.rgb * (1 – source.a))

To draw a sprite with this blend mode, we just need to use the following code (in the Draw event).

Unfortunately, this will prevent setting image_alpha at runtime!