Jump to content

Nearest Neighbor Scaling of Pixel Art in 2016


skeddles
 Share

Recommended Posts

Having a lot of trouble getting pixel art graphics to not be blurry. There are a lot of people having the same problems I find, but many articles are 2 or 3 years old.

I have tried canvas, pixi.js and phaser for displaying graphics, phonegap and Intel XDK for making apps.

Even solutions that work on desktop don't seem to work on mobile devices because of the higher dpi. It's still blurry. This method of scaling also apparently breaks touch input, so any solutions for that would be appreciated.

I think the main problem is the browser is scaling the canvas pixels up to match the dpi. I want to be able to work without anything getting scaled.

Is there any hope? It seems like something we should have solved by now...

Link to comment
Share on other sites

We have solved it.

If you use canvas you can set the resolution.

I have a (very very messy) example here that uses Pixi to set the scale mode. The key bits of that are simply setting the scale mode on the textures, which is a Pixi thing, and setting the resolution and using that resolution to size the canvas. It is scaled up with regard to the dpi and then scaled back down using CSS so that things remain consistent. This example is very terse, and it works because I want larger screens to simply show more of the game world, different requirements would possibly need slightly different solutions. I've done similar things with regular canvas rendering and using stackGL, although they were only tests.

What have you tried that isnt working right for you?

Link to comment
Share on other sites

1 hour ago, mattstyles said:

We have solved it.

If you use canvas you can set the resolution.

I have a (very very messy) example here that uses Pixi to set the scale mode. The key bits of that are simply setting the scale mode on the textures, which is a Pixi thing, and setting the resolution and using that resolution to size the canvas. It is scaled up with regard to the dpi and then scaled back down using CSS so that things remain consistent. This example is very terse, and it works because I want larger screens to simply show more of the game world, different requirements would possibly need slightly different solutions. I've done similar things with regular canvas rendering and using stackGL, although they were only tests.

What have you tried that isnt working right for you?

Hi, thanks for the reply

Not sure I'm understanding your code, so you're scaling the view up by the DPI, then using CSS to scale it to the size that fits the screen?

I would have thought you'd have to scale with css to width/dpi and height/dpi.

I actually tried that, and got something close to what I wanted.

The image is now at it's proper size (32x32 pixels), but it's blurry. And you'll also notice the screen is now only fills 1/4 of the screen. Which kind of makes sense but kind of doesn't

I must just be misunderstanding/missing something.

Here is my current code, based on the pixijs template supplied by XDK. I've implimented the css resizing piece of your code, but don't really understand the lower half, not sure how important that stuff is to resizing.

/* jshint browser:true */
/* globals PIXI, requestAnimationFrame */
(function() {

    document.addEventListener('DOMContentLoaded', function() {
        // create an new instance of a pixi stage
        var stage = new PIXI.Container();

        //get screen size variables
        var width = screen.availWidth;
        var height = screen.availHeight;
        var dpi = window.devicePixelRatio || 1;
        
        // create a renderer instance
        var renderer = new PIXI.autoDetectRenderer(width, height, {resolution: dpi,backgroundColor : 0xb4d2d7});
        renderer.view.width = width * window.devicePixelRatio;
        renderer.view.height = height * window.devicePixelRatio;
        renderer.view.style.width = width/dpi + 'px';
        renderer.view.style.height = height/dpi + 'px';
        renderer.view.style.display = 'block';
        renderer.view.style.position = 'absolute';
        renderer.view.style.top = 0;
        renderer.view.style.left = 0;
        renderer.view.style.zIndex = -1;

        // add the renderer view element to the DOM
        document.body.appendChild(renderer.view);

        requestAnimationFrame(animate);

        // create a texture from an image path
        var texture = PIXI.Texture.fromImage("lv1.png");

        // create a new Sprite using the texture
        var sprite = new PIXI.Sprite(texture);
        sprite.position.x = 10;
        sprite.position.y = 10;
        stage.addChild(sprite);
        
        //debug text
        var debugtext = new PIXI.Text('Screen Size: '+width+'x'+height+'\nDPI: '+dpi, {font: 'regular 10px Arial'});
        debugtext.position.x = 10;
        debugtext.position.y = 62;
        stage.addChild(debugtext);

        function animate() {
            requestAnimationFrame(animate);

            // render the stage
            renderer.render(stage);
        }
    }, false);

}());

 

Link to comment
Share on other sites

Don't divide the CSS scaling by the dpi, just set that to the your width. So you scale up to the pixel size, then back down to the screen size. Also, don't use screen.availWidth, you dont care how much is available, just use window.innerWidth as you want to know how much viewport you actually have, not how much you could have (you can't programmatically resize the browser window, barring fullscreen mode).

The rest of it (and it is messy, sorry, I dont have a better public example with everything all in one place) just makes sure that the canvas fills the window. It needs display block otherwise some browsers add an extra few pixels which results in scrollbars/scrolling (the same can happen with <img> elements and sometimes with inline styled elements). You might not want the z-index behind everything else, probably not.

Also, and I dont think it makes any difference for blurriness/interpolated pixels, but did you slap a viewport meta tag on for mobiles?

<meta name="viewport" content="width=device-width,initial-scale=1">

If you're scaling with PIXI you'll need to set that blend mode or it'll use default interpolation which will blur the image/s.

Link to comment
Share on other sites

1 hour ago, mattstyles said:

So you scale up to the pixel size

Oh I see, you are scaling it up, but setting the scalemode in pixi makes that okay.

1 hour ago, mattstyles said:

Also, and I dont think it makes any difference for blurriness/interpolated pixels, but did you slap a viewport meta tag on for mobiles?

Yeah I have

<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">

So now I have this, which is looking pretty good, nice and crisp. Though it's still 2x.

Now it looks good at it's normal resolution, but I want to scale it up more so the pixels are bigger. I can't figure out which numbers I need to multiply or divide though. This is also why I was thinking to divide by the dpi, because if it starts out at 2x I can only scale it up to 4x, 6x, 8x, 10x rather than 2x, 3x, 4x, 5x, etc

Link to comment
Share on other sites

You just scale the sprites up and down using sprite.scale to whatever size makes sense for your project. Obvs you'll have to use integers or you'll end up with odd sized 'pixels', and you'll probably want to use the same scaling for every sprite, you might be able to set it on a container or the stage, I guess that would apply to each child, not sure.

If you're after retro you can do an effect by scaling each 'pixel' up 3x (or even 9x) and then adding a filter to apply scanlines or some shading to each 'pixel' to turn each pixel into a pseudo-3d pixel, used with subtlety it can be a really good looking effect.

Link to comment
Share on other sites

1 minute ago, mattstyles said:

You just scale the sprites up and down using sprite.scale to whatever size makes sense for your project. Obvs you'll have to use integers or you'll end up with odd sized 'pixels'.

 

So I should scale up each sprite and round coordinates to a multiple of the scale factor? I was hoping there was a way to just program at a small resolution and scale the whole thing up, but if that's what I gotta do I can handle that.

Link to comment
Share on other sites

sprite.scale.set( 2, 2 )

sprite.scale.set( 3, 3 )

whatever you want (2.5 or 1.6, for example, wont work though).

I think you might get away with container.sprite( 4, 4 ) and have every child be 4x larger than its native size, but I havent tested that. edit: nope, of course, that does not work at all as all the positions also get multiplied, just set it on each sprite object, you should automate this by doing it in whatever sort of factory method you're using to create your sprite objects.

So, in that example I linked to earlier I have a sprite atlas where each sprite ended up 12x12 pixels, I just scaled them up 4x and lined them up horizontally 48px apart and they all stand side by side with no gap.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...