Simple shader to scale pixel art
SiegeLord

EDIT: Downloads updated

I was thinking about the best way to scale pixel art. Obviously any non-integral scale factor will produce "bad" results, but I wanted to see if I could come up with something that was better than nearest sampling and linear interpolation. The idea I had was to essentially do anti-aliasing on the image: i.e. treat each pixel of the source image as a square polygon and then draw an output pixel by blending between the source image pixels in proportion to the area of the output pixel that they cover. Here's a result of that line of thought... I think it looks better than both linear and nearest sampling and I think I'm going to use it for my games.

You're going to need a 5.1 branch Allegro to compile this (5.1.6 snapshot works). The code and shaders are in public domain.

Source
Windows Binary

To run the example binary you're going to need the D3D runtime installed to use the D3D backend (if you play video games made in this century you'll have it installed most likely), otherwise use allegro.cfg to specify the OpenGL backend ([graphics] driver = opengl).

Controls:

Left/Right - Switch the filtering method (nearest/linear/shader)
B - Toggle bitmap (Between a Sūpā Metoroido screenshot I lifted from Wikipedia and a pixel grid pattern)
Window border - You can resize the window!

Screenshots:

Nearest filtering:
{"name":"607339","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/0\/3\/03283239c326659faf9429d8653ecdbe.png","w":812,"h":631,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/0\/3\/03283239c326659faf9429d8653ecdbe"}607339
Linear filtering:
{"name":"607340","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/7\/9\/791531d4f0750c65e2d2509f0a0c46a3.png","w":812,"h":631,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/7\/9\/791531d4f0750c65e2d2509f0a0c46a3"}607340
Shader filtering:
{"name":"607338","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/1\/7\/174a467f732be49c17926e9bdcabdc41.png","w":812,"h":631,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/1\/7\/174a467f732be49c17926e9bdcabdc41"}607338

EDIT: There was a bug in the shader code that made the output not be identical to nearest filtering for integral scale factors. It is now fixed, I think.

EDIT2: Sample from the center of the pixel, not from the corner. Hopefully fixing the variability in output across platforms.

pkrcel

This look nice!

You are basicaly interpolating only at the "borders" of the scaled pixel (based on the actual area this scaled pixel covers in the resulting image)?

I've had a look at the GLSL code but I am not that keen on interpreting it.

SiegeLord
pkrcel said:

You are basicaly interpolating only at the "borders" of the scaled pixel (based on the actual area this scaled pixel covers in the resulting image)?

Yes, exactly.

Peter Wang

Looks quite nice in the screenshot but there's something wrong with the y offset here:
{"name":"607342","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/8\/b\/8b39f7a5e67af62cd9426259d5425f16.png","w":800,"h":600,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/8\/b\/8b39f7a5e67af62cd9426259d5425f16"}607342

I'm also curious why you need to recreate the shader on resize.

SiegeLord

Looks quite nice in the screenshot but there's something wrong with the y offset here:

Is this with OpenGL or Direct3D? I'm having bizzare outputs with Direct3D at the moment (on a different computer than where I tested this initially)... not 100% sure why.

Quote:

I'm also curious why you need to recreate the shader on resize.

It crashed for Direct3D otherwise... possibly an Allegro bug.

Kris Asick

You know, a similar effect can be achieved by first enlarging with nearest-neighbour filtering by 300% or 400%, then scaling back down with linear filtering. This is the technique I was going to use back when I was considering making some very-low-resolution games. ;)

SiegeLord

You know, a similar effect can be achieved by first enlarging with nearest-neighbour filtering by 300% or 400%, then scaling back down with linear filtering. This is the technique I was going to use back when I was considering making some very-low-resolution games. ;)

This is exactly the same, except you first (conceptually) enlarge it infinitely. In fact, this is how I first tested that this might produce something nice. The shader approach has the advantage of using less memory, however.

Trent Gamblin

Doesn't resizing the display cause a device reset? Pretty sure it does. If so, then you have to create shaders and any other GPU resources that Allegro doesn't do for you. So pretty much anything except bitmaps Allegro created.

Peter Wang

I was using OpenGL on Linux with nvidia proprietary driver.

As usual, it would be nice for Allegro to handle the device reset (and other platform specific quirks) for you - if it's reasonable.

SiegeLord

I was using OpenGL on Linux with nvidia proprietary driver.

That is disturbing. I'll have to run these shaders through more stringent tests rather than just compiling for my drivers... hopefully it's some error in the shader and not a fundamental limitation of the approach.

Mark Oates

Yea, that last one looks really nice. :)

Kris Asick
SiegeLord said:

This is exactly the same, except you first (conceptually) enlarge it infinitely. In fact, this is how I first tested that this might produce something nice. The shader approach has the advantage of using less memory, however.

I'll agree on the using less memory thing. ;)

I did discover at one point though that once you expand the image past the size you intend to resize to during enlargement, going any bigger has no effect on the image quality. IE: 320x240 -> 1280x960 -> 1024x768 looks exactly the same as 320x240 -> 2560x1920 -> 1024x768 :P

One advantage to doing it that way is you don't need to use shaders, granted, now that I actually know how to use shaders myself, I don't think I'd go back to not using them for things like this. 8-)

SiegeLord

Peter, could you re-download the shaders and try again? I altered how the texture sampling happens... it fixed my issues with D3D, so it might fix them for you as well.

Trent Gamblin

I don't know if this is anything like it, but one algorithm that produces pretty good results, especially when scaling up by a small amount is a weighted average scale. It'd probably be easy and fast with a shader...

Basically say for example you're scaling up by 1/4, so an extra pixel for every 3 pixels... the 4th pixel would be 1/2 taken from the 3rd pixel and 1/2 taken from the 5th (IIRC, it's been a while since I implemented it.)

It doesn't produce very much blur, at least compared to linear filtering.

EDIT: Have an old blog post showing some screenshots: http://www.nooskewl.com/content/monster-2-wiz-scaling

SiegeLord

EDIT: Have an old blog post showing some screenshots: http://www.nooskewl.com/content/monster-2-wiz-scaling

Yep, that's exactly the same algorithm, as far as I can tell.

Trent Gamblin

Ah. I had forgotten all about it TBH. I may have another crack at it in shaders instead of software at some point. (BTW, I got the idea from MattyMatt.)

Peter Wang
SiegeLord said:

Peter, could you re-download the shaders and try again? I altered how the texture sampling happens... it fixed my issues with D3D, so it might fix them for you as well.

Works.

Attached screenshots from a quick-ish hack of Dune Dynasty, 1.5x zoom. It's supposed have no effect at integer scales, yes? (Don't worry about the elements outside the game viewport; those are drawn at scale=2.0 but using the shader with scale=1.5.)

SiegeLord

It's supposed have no effect at integer scales, yes?

That's correct.

Quote:

Attached screenshots from a quick-ish hack of Dune Dynasty, 1.5x zoom.

Is the first one (*48.png) with the shader and the second with nearest?

Peter Wang

Yes. I should compare it with linear interpolation as well.

Thread #612318. Printed from Allegro.cc