Search code examples
swiftsprite-kitantialiasingskspritenodesktexture

SKTexture nearest filtering mode doesn't work (Making Pixel Art)


I used a relatively small image (44pixel tall) and scaled it up to have a pixel art look. I tried to change the filtering mode to nearest so the anti-aliasing would disappear but it didn't. It appears with blurry edges. I tried the same with a background getting the same result.

let myNode = SKSpriteNode()

class GameScene: SKScene {


    func makeNode() {

        myNode.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2)
        myNode.size = CGSize(width: 200, height: 200)
        myNode.texture = SKTexture(imageNamed: "image")
        myNode.texture!.filteringMode = .Nearest

        return self.addChild(myNode)
    }
}
override func didMoveToView(view: SKView) {

    makeNode()

}

Solution

  • UPDATE: SKTexture nearest filtering mode is now working in Xcode 7 GM. No need to manually scale images as I've suggested below.


    I am experiencing this same problem in Xcode 7 beta 5 with beta iOS 9 ; the Nearest filtering mode is seemingly ignored. In my project, scaling with nearest-neighbor scaling was previously working in Xcode 6 on iOS 8.

    In the event that the bug is not resolved before the final release of iOS 9 I am pre-scaling images before I place them in their respective atlases.

    To do this I wrote a simple python script to recursively find png files and scale them using imagmagick.

    If you don’t have imagemagick installed, you can install it using macports like so:

    sudo port install ImageMagick
    

    If you have homebrew it looks like this:

    brew install imagemagick
    

    I just place this script (below) in a file called imgscaler.py within the directory above my atlas files (which I want to scale by 400% in my case) and kick it off from the terminal:

    python imgscaler.py
    

    The script looks like this:

    import subprocess
    import os
    import sys
    import fnmatch
    
    
    def main():
        execution_folder_name = os.getcwd()
        file_extension_name = "png"
    
        #
        # Collect names of files that will be manipulated
        matches = []
        for root, dirNames, fileNames in os.walk(execution_folder_name):
            for filename in fnmatch.filter(fileNames, '*.' + file_extension_name):
                full_name = os.path.join(root, filename)
                matches.append(full_name)
    
        scale_percentage = "400"
    
        if not __query_yes_no("This script will scale images by " + scale_percentage + "% recursively from directory " + execution_folder_name + ".  Proceed?"):
            return
    
        #
        # Scale the images
        for match in matches:
            execution_str = "convert " + match + " -interpolate Nearest -filter point -resize " + scale_percentage + "% " + match + "\n"
            sys.stdout.write(execution_str)
            __run_command(execution_str)
    
    
    def __run_command(input_cmd):
        """Runs a command on the terminal, and returns the output in a string array.
        """
        process = subprocess.Popen(input_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
        output = []
        while True:
            line = process.stdout.readline()
            if line != '':
                output.append(line)
            else:
                break
    
        return output
    
    
    def __query_yes_no(question, default="yes"):
        """Asks a yes or no question, returns a true is answered "yes".
        """
        valid = {"yes": True, "y": True, "ye": True,
                 "no": False, "n": False}
        if default is None:
            prompt = " [y/n] "
        elif default == "yes":
            prompt = " [Y/n] "
        elif default == "no":
            prompt = " [y/N] "
        else:
            raise ValueError("invalid default answer: '%s'" % default)
    
        while True:
            sys.stdout.write(question + prompt)
            sys.stdout.flush()
            choice = raw_input().lower()
            if default is not None and choice == '':
                sys.stdout.write(default)
                sys.stdout.write('\n')
                sys.stdout.flush()
                return valid[default]
            elif choice in valid:
                sys.stdout.write(choice)
                sys.stdout.write('\n')
                sys.stdout.flush()
                return valid[choice]
            else:
                sys.stdout.write("Please respond with 'yes' or 'no' (or 'y' or 'n').\n")
                sys.stdout.flush()
    
    
    main()
    

    Change scale_percentage to whatever percentage you want to scale by.

    BTW, I am guessing this scaling issue will eventually get resolved. I’m currently updating my code with this assumption in mind. This is just a bandaid in the event the fix to nearest-neighbor scaling in SpriteKit arrives later than iOS 9.0.