Crop/Mask circular image node in sprite kit gives jagged edges

Is it possible give a circular mask/crop to an image node without jagged edges?

Following this example from Apple (https://developer.apple.com/reference/spritekit/skcropnode), the result is not ideal. You can click on the link to see.

  • Using custom SKNodes in spritekit scene editor
  • Returning object from callback in Swift
  • How to make extension for multiple classes Swift
  • How to update particular value of child in Firebase DB
  • How to perform some action on Play and Pause actions of AVPlayer?
  • Keep window always on top?
  •     let shapeNode = SKShapeNode()
        shapeNode.physicsBody = SKPhysicsBody(circleOfRadius: radius)
        shapeNode.physicsBody?.allowsRotation = false
        shapeNode.strokeColor = SKColor.clearColor()
    
        // Add a crop node to mask the profile image
        // profile images (start off with place holder)
        let scale = 1.0
        let profileImageNode = SKSpriteNode(imageNamed: "PlaceholderUser")
        profileImageNode.setScale(CGFloat(scale))
    
        let circlePath = CGPathCreateWithEllipseInRect(CGRectMake(-radius, -radius, radius*2, radius*2), nil)
    
        let circleMaskNode = SKShapeNode()
        circleMaskNode.path = circlePath
        circleMaskNode.zPosition = 12
        circleMaskNode.name = "connection_node"
        circleMaskNode.fillColor = SKColor.whiteColor()
        circleMaskNode.strokeColor = SKColor.clearColor()
    
        let zoom = SKAction.fadeInWithDuration(0.25)
        circleMaskNode.runAction(zoom)
    
        let cropNode = SKCropNode()
        cropNode.maskNode = circleMaskNode
        cropNode.addChild(profileImageNode)
        cropNode.position = shapeNode.position
    
        shapeNode.addChild(cropNode)
        self.addChild(shapeNode)
    

    2 Solutions Collect From Internet About “Crop/Mask circular image node in sprite kit gives jagged edges”

    UPDATE:

    Ok, so here’s one solution I came up. Not super ideal but it works perfectly. Essentially, I have a to size/scale, and cut the image exactly the way it would go on the SKSpriteNode so I would not have to use SKCropNode or some variation of SKShapeNode.

    1. I used these UIImage extensions by Leo Dabus to resize/shape the image exactly as needed. Cut a UIImage into a circle Swift(iOS)

      var circle: UIImage? {
           let square = CGSize(width: min(size.width, size.height), height: min(size.width, size.height))
           let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: square))
           imageView.contentMode = .ScaleAspectFill
           imageView.image = self
           imageView.layer.cornerRadius = square.width/2
           imageView.layer.masksToBounds = true
           UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
           guard let context = UIGraphicsGetCurrentContext() else { return nil }
           imageView.layer.renderInContext(context)
           let result = UIGraphicsGetImageFromCurrentImageContext()
           UIGraphicsEndImageContext()
           return result
      }
      
      func resizedImageWithinRect(rectSize: CGSize) -> UIImage {
           let widthFactor = size.width / rectSize.width
           let heightFactor = size.height / rectSize.height
      
           var resizeFactor = widthFactor
           if size.height > size.width {
           resizeFactor = heightFactor
           }
      
           let newSize = CGSizeMake(size.width/resizeFactor, size.height/resizeFactor)
           let resized = resizedImage(newSize)
           return resized
      }
      
    2. The final codes look like this:

      //create/shape image
      let image = UIImage(named: "TestImage")
      let scaledImage = image?.resizedImageWithinRect(CGSize(width: 100, height: 100))
      let circleImage = scaledImage?.circle
      
      //create sprite
      let sprite = SKSpriteNode(texture: SKTexture(image: circleImage!))
      sprite.position = CGPoint(x: view.frame.width/2, y: view.frame.height/2)
      
      //set texture/image
      sprite.texture = SKTexture(image: circleImage!)
      sprite.physicsBody = SKPhysicsBody(texture: SKTexture(image: circleImage!), size: CGSizeMake(100, 100))
      if let physics = sprite.physicsBody {
          //add the physic properties
      }
      
      //scale node
      sprite.setScale(1.0)
      addChild(sprite)
      

    So if you have a perfectly scaled asset/image, then you probably dont need to do all this work, but I’m getting images from the backend that could come in any sizes.

    There are two different techniques that can be combined to reduce the aliasing of edges created from cropping.

    1. Create bigger images than you need, and then scale them down. Both the target (to be cropped) and the mask. Perform the cropping action, then scale down to required size.

    2. Use very subtle blurring of the cropping shape, to soften its edges. This is best done in Photoshop or a similar editing program, to taste and need.

    When these two techniques are combined, the results can be very good.