Scale 2D pixel art games using surfaces to avoid pixel decimation in GameMaker Studio 2

Much has been written about resolution scaling in pixel art games. It usually comes down to this simplistic rule: always resize 2D games by integer values (2x, 3x, 4x, 5x, etc) so pixel art will always look correct.

I wrote that myself; to make a good looking low-res pixel art game on modern monitors, you should stick with a 384×216 resolution and scale it up 5 times to get a perfect 1920×1080 (1080p) game.

That’s still true-ish. But the problem I was trying to address wasn’t pixel distortion. It was pixel decimation. Let’s see how to solve it using any resolution you want to use.

What is Pixel Decimation

When you resize an image to non integers multiples, GameMaker has to figure out where to draw the original pixels inside the new, bigger grid. And since there is no such thing as half a pixel, distortion occurs.

Let’s take this image, for example. It’s a 390×220 pattern I created in Photoshop to illustrate the issue.

390 x 220 black pixel pattern. Each black pixel has the same size.

Now, my game consists of just this image, stretched to fullscreen (what a fun game!). i.e. the view is set to the size of the image, but then I run the game in fullscreen.

Since my notebook display resolution is 1366×768, how is GameMaker supposed to scale a 390×220 image? The display resolution is more or less 3.5 times that of the image; it’s not an integer multiple.

And so pixel decimation occurs.

Pixels are being decimated. This is the worst possible kind of distortion for a Pixel Art game.

Pixels are being, literally, decimated. Some lines (both columns and rows) are being skipped entirely while others are being drawn twice to fill the gaps (or at least that’s what it looks like).

We’re losing details. This is because GameMaker’s trying to draw a 390×220 picture inside a 1366×768 surface (which is 3.5 times larger). The 3.5 multiplier is wrecking havoc. It’s not 3. It’s not 4. It’s 3.5.

A plausible solution

Resize the application surface to be the same size of the view, and then draw it scaled to the display width and height. Some distortion is still there (some pixels may not appear square) but all pixels will be there.

In this case I resize the application surface to be exactly 390×220 (same as the view) before drawing to it (i.e. at the start of the game). Then I use the draw_surface_stretched function to draw that surface stretched to fullscreen. GMS is still making a 3.5 times stretch, but in this case pixels have already been drawn to the application_surface. So the only distortion that will happen, will be about some pixels aspect ratio.

Since the surface is now the same size of the view, each pixel actually gets drawn in its correct place. This is way better than pixel decimation because, at least, we don’t lose detail.

Now GameMaker simply stretches the surface to cover the screen area.

Some pixels are not perfectly square, but all pixels are there. This is the closest to perfect scaling you will ever get using non integers multiples.

Please note that this is almost exactly the same distortion that would happen if you were to resize the original picture with Photoshop.

Photoshop resize from 390×220 to 1366×768 with nearest neighbor resampling. I’d say it’s even worse than GameMaker’s resize.

There’s still a sampling problem, albeit a different one; it’s just a matter of personal preference then.

To avoid pixel decimation (but still get some pixel distortion) you can use this method then. But if you absolutely, positively need no compromise, and you want perfectly square pixels with no distortions at all… then you’re left with the integer multiplier solution (i.e. you have to use an integer multiple of the final display resolution)

Real Example

Since games are not evenly spaced black pixels (thank God), the worst kind of effect tends to happen with pixel decimation (especially in moving images).

Entire lines (rows and columns) of pixels appears and disappears everywhere in the game during camera movements. You can even see the effects in still images as well. Look at the example below: the white reflection on the head of the player is gone in the right image. Also other lines are being skipped.

On the left you can see no apparent artifact. On the right you lose entire columns of pixels. Both games have their view set to 390×220. Then they are resized to 1366×768. Just with a different method.

This is great but where’s the code?

The code is simple. First you have to disable the drawing of the application surface.

// Place this in your controller create event.
application_surface_draw_enable(false)

Then resize the application surface to match the view of your game.

// Use your own WIDTH and HEIGHT
surface_resize(application_surface, VIEW_WIDTH, VIEW_HEIGHT)

Now go inside the draw_post event of your controller and draw the stretched surface.

// Use your display sizes
draw_surface_stretched(application_surface, 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)

Done. Please note that this method does not solve issues relative to wrong aspect ratio; I’ll write another post about that. This method only deals with pixel decimation.

Special thanks to Luis Zuno aka Ansimuz for providing the graphics you see above. Get this and other great free asset packs at pixelgameart.org

Liked it? Take a second to support Nikles on Patreon!

2 thoughts on “Scale 2D pixel art games using surfaces to avoid pixel decimation in GameMaker Studio 2”

What are your thoughts on this?